{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":113558,"databundleVersionId":14174843,"sourceType":"competition"}],"dockerImageVersionId":31154,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"<div style=\"display: flex; justify-content: space-between; align-items: flex-start;\">\n    <div style=\"text-align: left;\">\n        <p style=\"color:#FFD700; font-size: 15px; font-weight: bold; margin-bottom: 1px; text-align: left;\">Published on  November 7, 2025</p>\n        <h4 style=\"color:#4B0082; font-weight: bold; text-align: left; margin-top: 6px;\">Author: Jocelyn C. Dumlao</h4>\n        <p style=\"font-size: 17px; line-height: 1.7; color: #333; text-align: center; margin-top: 20px;\"></p>\n        <a href=\"https://www.linkedin.com/in/jocelyn-dumlao-168921a8/\" target=\"_blank\" style=\"display: inline-block; background-color: #003f88; color: #fff; text-decoration: none; padding: 5px 10px; border-radius: 10px; margin: 15px;\">LinkedIn</a>\n        <a href=\"https://github.com/jcdumlao14\" target=\"_blank\" style=\"display: inline-block; background-color: transparent; color: #059c99; text-decoration: none; padding: 5px 10px; border-radius: 10px; margin: 15px; border: 2px solid #007bff;\">GitHub</a>\n        <a href=\"https://www.youtube.com/@CogniCraftedMinds\" target=\"_blank\" style=\"display: inline-block; background-color: #ff0054; color: #fff; text-decoration: none; padding: 5px 10px; border-radius: 10px; margin: 15px;\">YouTube</a>\n        <a href=\"https://www.kaggle.com/jocelyndumlao\" target=\"_blank\" style=\"display: inline-block; background-color: #3a86ff; color: #fff; text-decoration: none; padding: 5px 10px; border-radius: 10px; margin: 15px;\">Kaggle</a>\n    </div>\n</div>","metadata":{}},{"cell_type":"markdown","source":"# <div style=\"color:white;display:inline-block;border-radius:5px;background-color:#009688 ;font-family:Nexa;overflow:hidden\"><p style=\"padding:10px;color:white;overflow:hidden;font-size:85%;letter-spacing:0.5px;margin:0;border: 6px groove #ffd700;\"><b> </b>Imports Libraries</p></div>\n","metadata":{}},{"cell_type":"code","source":"import os\nimport random\nfrom glob import glob\nimport json\nfrom pathlib import Path\n\nimport numpy as np\nimport pandas as pd\nfrom matplotlib import pyplot as plt\n\nimport cv2\nfrom sklearn.model_selection import train_test_split\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\n\n\nimport warnings\nwarnings.filterwarnings(\"ignore\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-07T05:48:21.746514Z","iopub.execute_input":"2025-11-07T05:48:21.747298Z","iopub.status.idle":"2025-11-07T05:48:24.198474Z","shell.execute_reply.started":"2025-11-07T05:48:21.747266Z","shell.execute_reply":"2025-11-07T05:48:24.197440Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:white;display:inline-block;border-radius:5px;background-color:#009688 ;font-family:Nexa;overflow:hidden\"><p style=\"padding:10px;color:white;overflow:hidden;font-size:85%;letter-spacing:0.5px;margin:0;border: 6px groove #ffd700;\"><b> </b>Configuration</p></div>","metadata":{}},{"cell_type":"code","source":"# Configuration\n\nSEED = 42\nrandom.seed(SEED)\nnp.random.seed(SEED)\ntorch.manual_seed(SEED)\n\nIMG_SIZE = 256\nBATCH_SIZE = 8\nEPOCHS = 10\nLR = 1e-4\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nDATA_PATH = \"/kaggle/input/recodai-luc-scientific-image-forgery-detection/\"  # change if needed\n\nprint(f\"Device: {DEVICE}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-07T05:48:28.643471Z","iopub.execute_input":"2025-11-07T05:48:28.644322Z","iopub.status.idle":"2025-11-07T05:48:28.677081Z","shell.execute_reply.started":"2025-11-07T05:48:28.644298Z","shell.execute_reply":"2025-11-07T05:48:28.676429Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:white;display:inline-block;border-radius:5px;background-color:#009688 ;font-family:Nexa;overflow:hidden\"><p style=\"padding:10px;color:white;overflow:hidden;font-size:85%;letter-spacing:0.5px;margin:0;border: 6px groove #ffd700;\"><b> </b>Dataset</p></div>","metadata":{}},{"cell_type":"code","source":"# Dataset\n\nclass ForgeryDataset(Dataset):\n    \"\"\"Dataset that yields (image_tensor, mask_tensor) where mask is 1-channel binary.\"\"\"\n    def __init__(self, image_paths, mask_paths=None, is_authentic=None, img_size=IMG_SIZE):\n        self.image_paths = list(image_paths)\n        self.mask_paths = list(mask_paths) if mask_paths is not None else [None] * len(self.image_paths)\n        self.is_authentic = list(is_authentic) if is_authentic is not None else [False] * len(self.image_paths)\n        self.img_size = img_size\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 = cv2.imread(img_path)\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        img = cv2.resize(img, (self.img_size, self.img_size)).astype(np.float32) / 255.0\n        img = torch.from_numpy(img).permute(2, 0, 1).float()  # C,H,W\n\n        if self.is_authentic[idx]:\n            mask = np.zeros((self.img_size, self.img_size), dtype=np.float32)\n        else:\n            mask_path = self.mask_paths[idx]\n            mask = np.load(mask_path)\n            if mask.ndim == 3:\n                mask = mask[0] if mask.shape[0] == 1 else mask[:, :, 0]\n            mask = (mask > 0).astype(np.float32)\n            mask = cv2.resize(mask, (self.img_size, self.img_size), interpolation=cv2.INTER_NEAREST)\n\n        mask = torch.from_numpy(mask).unsqueeze(0).float()  # 1,H,W\n        return img, mask\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-07T05:48:32.321168Z","iopub.execute_input":"2025-11-07T05:48:32.321754Z","iopub.status.idle":"2025-11-07T05:48:32.329187Z","shell.execute_reply.started":"2025-11-07T05:48:32.321729Z","shell.execute_reply":"2025-11-07T05:48:32.328463Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:white;display:inline-block;border-radius:5px;background-color:#009688 ;font-family:Nexa;overflow:hidden\"><p style=\"padding:10px;color:white;overflow:hidden;font-size:85%;letter-spacing:0.5px;margin:0;border: 6px groove #ffd700;\"><b> </b>Lightweight UNet</p></div>","metadata":{}},{"cell_type":"code","source":"# Lightweight UNet\n\ndef conv_block(in_ch, out_ch):\n    return nn.Sequential(\n        nn.Conv2d(in_ch, out_ch, kernel_size=3, padding=1),\n        nn.ReLU(inplace=True),\n        nn.Conv2d(out_ch, out_ch, kernel_size=3, padding=1),\n        nn.ReLU(inplace=True),\n    )\n\nclass SmallUNet(nn.Module):\n    def __init__(self):\n        super().__init__()\n        self.enc1 = conv_block(3, 32)\n        self.pool1 = nn.MaxPool2d(2)\n        self.enc2 = conv_block(32, 64)\n        self.pool2 = nn.MaxPool2d(2)\n        self.enc3 = conv_block(64, 128)\n\n        self.up2 = nn.ConvTranspose2d(128, 64, kernel_size=2, stride=2)\n        self.dec2 = conv_block(128, 64)\n        self.up1 = nn.ConvTranspose2d(64, 32, kernel_size=2, stride=2)\n        self.dec1 = conv_block(64, 32)\n\n        self.out = nn.Conv2d(32, 1, kernel_size=1)\n        self.sigmoid = nn.Sigmoid()\n\n    def forward(self, x):\n        e1 = self.enc1(x)\n        p1 = self.pool1(e1)\n        e2 = self.enc2(p1)\n        p2 = self.pool2(e2)\n        e3 = self.enc3(p2)\n\n        u2 = self.up2(e3)\n        cat2 = torch.cat([u2, e2], dim=1)\n        d2 = self.dec2(cat2)\n\n        u1 = self.up1(d2)\n        cat1 = torch.cat([u1, e1], dim=1)\n        d1 = self.dec1(cat1)\n\n        out = self.out(d1)\n        return self.sigmoid(out)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-07T05:48:39.205880Z","iopub.execute_input":"2025-11-07T05:48:39.206508Z","iopub.status.idle":"2025-11-07T05:48:39.213710Z","shell.execute_reply.started":"2025-11-07T05:48:39.206482Z","shell.execute_reply":"2025-11-07T05:48:39.212896Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:white;display:inline-block;border-radius:5px;background-color:#009688 ;font-family:Nexa;overflow:hidden\"><p style=\"padding:10px;color:white;overflow:hidden;font-size:85%;letter-spacing:0.5px;margin:0;border: 6px groove #ffd700;\"><b> </b>RLE encode </p></div>","metadata":{}},{"cell_type":"code","source":"# Utility: RLE encode \n\ndef _rle_one(arr):\n    \"\"\"Encode a single 2D binary mask into RLE list of pairs.\"\"\"\n    dots = np.where(arr.T.flatten() == 1)[0]\n    if len(dots) == 0:\n        return []\n    run_lengths = []\n    prev = -2\n    for b in dots:\n        if b > prev + 1:\n            run_lengths.extend((b + 1, 0))\n        run_lengths[-1] += 1\n        prev = b\n    return run_lengths\n\ndef rle_encode(masks, fg_val=1):\n    return ';'.join(json.dumps(_rle_one(m)) for m in masks)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-07T05:48:45.262934Z","iopub.execute_input":"2025-11-07T05:48:45.263637Z","iopub.status.idle":"2025-11-07T05:48:45.268452Z","shell.execute_reply.started":"2025-11-07T05:48:45.263589Z","shell.execute_reply":"2025-11-07T05:48:45.267776Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:white;display:inline-block;border-radius:5px;background-color:#009688 ;font-family:Nexa;overflow:hidden\"><p style=\"padding:10px;color:white;overflow:hidden;font-size:85%;letter-spacing:0.5px;margin:0;border: 6px groove #ffd700;\"><b> </b>Load file lists & split</p></div>","metadata":{}},{"cell_type":"code","source":"# Load file lists & split \n\nforged_images = sorted(glob(os.path.join(DATA_PATH, 'train_images/forged/*.png')))\ntrain_masks = sorted(glob(os.path.join(DATA_PATH, 'train_masks/*.npy')))\nauthentic_images = sorted(glob(os.path.join(DATA_PATH, 'train_images/authentic/*.png')))\n\nmatched_forged_images, matched_masks = [], []\nfor img_path in forged_images:\n    img_name = os.path.basename(img_path).replace('.png', '')\n    mask_path = os.path.join(DATA_PATH, f'train_masks/{img_name}.npy')\n    if os.path.exists(mask_path):\n        matched_forged_images.append(img_path)\n        matched_masks.append(mask_path)\n\nall_images = matched_forged_images + authentic_images\nall_masks = matched_masks + [None] * len(authentic_images)\nall_is_authentic = [False] * len(matched_forged_images) + [True] * len(authentic_images)\n\nprint(f\"Total images: {len(all_images)} | Forged: {len(matched_forged_images)} | Authentic: {len(authentic_images)}\")\n\n# stratify safely\nif len(set(all_is_authentic)) < 2:\n    print(\"⚠️ Not enough class diversity — using non-stratified split.\")\n    stratify_arg = None\nelse:\n    stratify_arg = all_is_authentic\n\ntrain_imgs, val_imgs, train_masks_split, val_masks_split, train_auth, val_auth = train_test_split(\n    all_images, all_masks, all_is_authentic,\n    test_size=0.2, random_state=SEED, stratify=stratify_arg\n)\n\ntrain_ds = ForgeryDataset(train_imgs, train_masks_split, train_auth)\nval_ds = ForgeryDataset(val_imgs, val_masks_split, val_auth)\n\ntrain_loader = DataLoader(train_ds, batch_size=BATCH_SIZE, shuffle=True, num_workers=2, pin_memory=True)\nval_loader = DataLoader(val_ds, batch_size=BATCH_SIZE, shuffle=False, num_workers=2, pin_memory=True)\n\nprint(f\"✅ Dataloaders ready: {len(train_ds)} train samples, {len(val_ds)} val samples.\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-07T05:48:50.095646Z","iopub.execute_input":"2025-11-07T05:48:50.096209Z","iopub.status.idle":"2025-11-07T05:48:54.586647Z","shell.execute_reply.started":"2025-11-07T05:48:50.096186Z","shell.execute_reply":"2025-11-07T05:48:54.585866Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:white;display:inline-block;border-radius:5px;background-color:#009688 ;font-family:Nexa;overflow:hidden\"><p style=\"padding:10px;color:white;overflow:hidden;font-size:85%;letter-spacing:0.5px;margin:0;border: 6px groove #ffd700;\"><b> </b>Training Utilities</p></div>","metadata":{}},{"cell_type":"code","source":"# Training utilities\n\ndef train_one_epoch(model, loader, criterion, optimizer, device):\n    model.train()\n    running = 0.0\n    for imgs, masks in loader:\n        imgs = imgs.to(device)\n        masks = masks.to(device)\n        preds = model(imgs)\n        loss = criterion(preds, masks)\n        optimizer.zero_grad()\n        loss.backward()\n        optimizer.step()\n        running += loss.item()\n    return running / len(loader)\n\n@torch.no_grad()\ndef validate(model, loader, criterion, device):\n    model.eval()\n    running = 0.0\n    for imgs, masks in loader:\n        imgs = imgs.to(device)\n        masks = masks.to(device)\n        preds = model(imgs)\n        loss = criterion(preds, masks)\n        running += loss.item()\n    return running / len(loader)\n\ndef pixel_f1(pred_mask, gt_mask):\n    pred = (pred_mask > 0.5).astype(np.uint8).ravel()\n    gt = (gt_mask > 0.5).astype(np.uint8).ravel()\n    tp = np.sum((pred == 1) & (gt == 1))\n    fp = np.sum((pred == 1) & (gt == 0))\n    fn = np.sum((pred == 0) & (gt == 1))\n    if tp + 0.5*(fp+fn) == 0:\n        return 0.0\n    return 2*tp / (2*tp + fp + fn)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-07T05:48:59.792585Z","iopub.execute_input":"2025-11-07T05:48:59.793360Z","iopub.status.idle":"2025-11-07T05:48:59.800123Z","shell.execute_reply.started":"2025-11-07T05:48:59.793334Z","shell.execute_reply":"2025-11-07T05:48:59.799247Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:white;display:inline-block;border-radius:5px;background-color:#009688 ;font-family:Nexa;overflow:hidden\"><p style=\"padding:10px;color:white;overflow:hidden;font-size:85%;letter-spacing:0.5px;margin:0;border: 6px groove #ffd700;\"><b> </b>Instantiate Model, Loss, Optimizer</p></div>","metadata":{}},{"cell_type":"code","source":"# Instantiate model, loss, optimizer\n\nmodel = SmallUNet().to(DEVICE)\ncriterion = nn.BCELoss()\noptimizer = optim.Adam(model.parameters(), lr=LR)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-07T05:49:05.511905Z","iopub.execute_input":"2025-11-07T05:49:05.512714Z","iopub.status.idle":"2025-11-07T05:49:06.777231Z","shell.execute_reply.started":"2025-11-07T05:49:05.512688Z","shell.execute_reply":"2025-11-07T05:49:06.776649Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:white;display:inline-block;border-radius:5px;background-color:#009688 ;font-family:Nexa;overflow:hidden\"><p style=\"padding:10px;color:white;overflow:hidden;font-size:85%;letter-spacing:0.5px;margin:0;border: 6px groove #ffd700;\"><b> </b>Training Loop</p></div>","metadata":{}},{"cell_type":"code","source":"# Training loop\n\ntrain_losses, val_losses = [], []\nprint(\"🚀 Training started...\")\nfor epoch in range(EPOCHS):\n    tr_loss = train_one_epoch(model, train_loader, criterion, optimizer, DEVICE)\n    val_loss = validate(model, val_loader, criterion, DEVICE)\n    train_losses.append(tr_loss)\n    val_losses.append(val_loss)\n    print(f\"Epoch {epoch+1}/{EPOCHS} — Train Loss: {tr_loss:.4f} | Val Loss: {val_loss:.4f}\")\n\nplt.figure(figsize=(6,4))\nplt.plot(train_losses, label='train')\nplt.plot(val_losses, label='val')\nplt.title(\"Loss Curve\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"BCE Loss\")\nplt.legend()\nplt.tight_layout()\nplt.show()\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-07T05:49:10.482491Z","iopub.execute_input":"2025-11-07T05:49:10.483393Z","iopub.status.idle":"2025-11-07T06:08:23.461039Z","shell.execute_reply.started":"2025-11-07T05:49:10.483364Z","shell.execute_reply":"2025-11-07T06:08:23.460155Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:white;display:inline-block;border-radius:5px;background-color:#009688 ;font-family:Nexa;overflow:hidden\"><p style=\"padding:10px;color:white;overflow:hidden;font-size:85%;letter-spacing:0.5px;margin:0;border: 6px groove #ffd700;\"><b> </b>Visualization</p></div>","metadata":{}},{"cell_type":"code","source":"# visualization utilities\n\ndef _mask_contours(mask):\n    mask_u8 = (mask.astype(np.uint8) * 255).astype(np.uint8)\n    contours, _ = cv2.findContours(mask_u8, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)\n    result = []\n    for c in contours:\n        c = c.squeeze()\n        if c.ndim == 1:\n            continue\n        result.append(c)\n    return result\n\ndef overlay_mask_on_image(img_rgb, mask, color=(0,255,0), alpha=0.4):\n    if img_rgb.dtype == np.float32 or img_rgb.dtype == np.float64:\n        bg = (img_rgb * 255).astype(np.uint8).copy()\n    else:\n        bg = img_rgb.copy()\n    overlay = bg.copy()\n    cv2.drawContours(overlay, [c.astype(np.int32) for c in _mask_contours(mask)],\n                     -1, color, thickness=-1)\n    blended = cv2.addWeighted(overlay, alpha, bg, 1-alpha, 0)\n    return blended\n\ndef visualize_predictions(model, image_paths, mask_paths, is_authentic, device=DEVICE, num_samples=6):\n    model.eval()\n    indices = list(range(len(image_paths)))\n    chosen = random.sample(indices, min(num_samples, len(indices)))\n    rows = len(chosen)\n    plt.figure(figsize=(12, 4*rows))\n\n    with torch.no_grad():\n        for i, idx in enumerate(chosen):\n            img_path = image_paths[idx]\n            img = cv2.imread(img_path)\n            img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n            orig_h, orig_w = img_rgb.shape[:2]\n\n            img_resized = cv2.resize(img_rgb, (IMG_SIZE, IMG_SIZE)).astype(np.float32) / 255.0\n            tensor = torch.from_numpy(img_resized).permute(2,0,1).unsqueeze(0).float().to(device)\n            pred = model(tensor).cpu().numpy()[0,0]\n            pred_resized = cv2.resize(pred, (orig_w, orig_h), interpolation=cv2.INTER_LINEAR)\n            pred_binary = (pred_resized > 0.5).astype(np.uint8)\n\n            if is_authentic[idx]:\n                gt_mask = np.zeros((orig_h, orig_w), dtype=np.uint8)\n                label = \"AUTHENTIC\"\n            else:\n                gt = np.load(mask_paths[idx])\n                if gt.ndim == 3:\n                    gt = gt[0] if gt.shape[0] == 1 else gt[:,:,0]\n                gt = (gt > 0).astype(np.uint8)\n                gt_mask = cv2.resize(gt, (orig_w, orig_h), interpolation=cv2.INTER_NEAREST)\n                label = \"FORGED\"\n\n            forged_pixels = int(gt_mask.sum())\n            forged_pct = forged_pixels / gt_mask.size * 100\n\n            heat = cv2.applyColorMap((pred_resized*255).astype(np.uint8), cv2.COLORMAP_JET)\n            heat = cv2.cvtColor(heat, cv2.COLOR_BGR2RGB)\n\n            gt_overlay = overlay_mask_on_image(img_rgb, gt_mask, color=(0,255,0), alpha=0.35)\n            pred_overlay = img_rgb.copy()\n            contours = _mask_contours(pred_binary)\n            if contours:\n                cv2.drawContours(pred_overlay, contours, -1, (255,0,0), thickness=2)\n\n            ax = plt.subplot(rows, 3, i*3 + 1)\n            ax.imshow(img_rgb)\n            ax.set_title(f\"{label} - {os.path.basename(img_path)}\")\n            ax.axis(\"off\")\n\n            ax = plt.subplot(rows, 3, i*3 + 2)\n            ax.imshow(gt_overlay)\n            ax.set_title(f\"Ground Truth — {forged_pixels:,} px ({forged_pct:.2f}%)\")\n            ax.axis(\"off\")\n\n            ax = plt.subplot(rows, 3, i*3 + 3)\n            blended = (0.6 * img_rgb.astype(np.float32) + 0.4 * heat.astype(np.float32)).astype(np.uint8)\n            if contours:\n                cv2.drawContours(blended, contours, -1, (255,0,0), thickness=2)\n            ax.imshow(blended)\n            ax.set_title(\"Prediction heatmap + binary contour\")\n            ax.axis(\"off\")\n\n    plt.tight_layout()\n    plt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-07T06:10:18.522823Z","iopub.execute_input":"2025-11-07T06:10:18.523150Z","iopub.status.idle":"2025-11-07T06:10:18.539418Z","shell.execute_reply.started":"2025-11-07T06:10:18.523123Z","shell.execute_reply":"2025-11-07T06:10:18.538519Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Visualize predictions\n\nprint(\"🔍 Visualizing some training samples...\")\nvisualize_predictions(model, train_imgs, train_masks_split, train_auth, device=DEVICE, num_samples=6)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-07T06:10:27.923803Z","iopub.execute_input":"2025-11-07T06:10:27.924087Z","iopub.status.idle":"2025-11-07T06:10:36.748550Z","shell.execute_reply.started":"2025-11-07T06:10:27.924068Z","shell.execute_reply":"2025-11-07T06:10:36.747658Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:white;display:inline-block;border-radius:5px;background-color:#009688 ;font-family:Nexa;overflow:hidden\"><p style=\"padding:10px;color:white;overflow:hidden;font-size:85%;letter-spacing:0.5px;margin:0;border: 6px groove #ffd700;\"><b> </b>Validation F1</p></div>","metadata":{}},{"cell_type":"code","source":"# Validation F1\n\nprint(\"📏 Computing validation F1...\")\nmodel.eval()\nval_f1s = []\nwith torch.no_grad():\n    for idx in range(len(val_imgs)):\n        img_path = val_imgs[idx]\n        img = cv2.imread(img_path)\n        img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        orig_h, orig_w = img_rgb.shape[:2]\n        img_resized = cv2.resize(img_rgb, (IMG_SIZE, IMG_SIZE)).astype(np.float32) / 255.0\n        tensor = torch.from_numpy(img_resized).permute(2,0,1).unsqueeze(0).float().to(DEVICE)\n        pred = model(tensor).cpu().numpy()[0,0]\n        pred_resized = cv2.resize(pred, (orig_w, orig_h), interpolation=cv2.INTER_LINEAR)\n        pred_binary = (pred_resized > 0.5).astype(np.uint8)\n\n        if val_auth[idx]:\n            gt_mask = np.zeros((orig_h, orig_w), dtype=np.uint8)\n        else:\n            gt = np.load(val_masks_split[idx])\n            if gt.ndim == 3:\n                gt = gt[0] if gt.shape[0] == 1 else gt[:,:,0]\n            gt_mask = (gt > 0).astype(np.uint8)\n            gt_mask = cv2.resize(gt_mask, (orig_w, orig_h), interpolation=cv2.INTER_NEAREST)\n\n        f1 = pixel_f1(pred_binary, gt_mask)\n        val_f1s.append(f1)\n\nprint(f\"✅ Mean pixel-wise F1 on validation: {np.mean(val_f1s):.4f}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-07T06:11:25.041721Z","iopub.execute_input":"2025-11-07T06:11:25.042498Z","iopub.status.idle":"2025-11-07T06:12:11.065270Z","shell.execute_reply.started":"2025-11-07T06:11:25.042470Z","shell.execute_reply":"2025-11-07T06:12:11.064581Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:white;display:inline-block;border-radius:5px;background-color:#009688 ;font-family:Nexa;overflow:hidden\"><p style=\"padding:10px;color:white;overflow:hidden;font-size:85%;letter-spacing:0.5px;margin:0;border: 6px groove #ffd700;\"><b> </b>Test Prediction & Submission </p></div>","metadata":{}},{"cell_type":"code","source":"# Test prediction & submission \n\nprint(\"🧾 Generating submission.csv...\")\ntest_image_paths = sorted(glob(os.path.join(DATA_PATH, 'test_images/*.png')))\ntest_predictions = {}\n\nwith torch.no_grad():\n    for img_path in test_image_paths:\n        img = cv2.imread(img_path)\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        orig_h, orig_w = img.shape[:2]\n        img_resized = cv2.resize(img, (IMG_SIZE, IMG_SIZE)).astype(np.float32) / 255.0\n        tensor = torch.from_numpy(img_resized).permute(2,0,1).unsqueeze(0).float().to(DEVICE)\n        pred = model(tensor).cpu().numpy()[0,0]\n        pred_resized = cv2.resize(pred, (orig_w, orig_h), interpolation=cv2.INTER_LINEAR)\n        pred_binary = (pred_resized > 0.5).astype(np.uint8)\n\n        image_id = os.path.splitext(os.path.basename(img_path))[0]\n        if pred_binary.sum() == 0:\n            test_predictions[image_id] = \"authentic\"\n        else:\n            rle = rle_encode([pred_binary], fg_val=1)\n            test_predictions[image_id] = rle\n\nsubmission_df = pd.DataFrame([{\"case_id\": k, \"annotation\": v} for k, v in test_predictions.items()])\nsubmission_df.to_csv(\"submission.csv\", index=False)\nprint(\"✅ submission.csv saved!\")\nprint(submission_df.head())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-07T06:12:25.204927Z","iopub.execute_input":"2025-11-07T06:12:25.205665Z","iopub.status.idle":"2025-11-07T06:12:25.283575Z","shell.execute_reply.started":"2025-11-07T06:12:25.205642Z","shell.execute_reply":"2025-11-07T06:12:25.282952Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}