{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":113558,"databundleVersionId":14174843,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":13494653,"sourceType":"datasetVersion","datasetId":8567918}],"dockerImageVersionId":31154,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## To know about the trained model, see this note book [Image Forgery Detection 1](https://www.kaggle.com/code/abdulahadshaik/image-forgery-detection-1)","metadata":{}},{"cell_type":"code","source":"# ====================================================\n# Recod.ai/LUC - Enhanced Inference Notebook\n# ----------------------------------------------------\n# Multi-GPU + AMP + Tile-Based Inference + Cleanup\n# ====================================================\n\nimport os, gc, json, cv2, warnings\nimport numpy as np\nimport pandas as pd\nfrom glob import glob\nfrom tqdm.auto import tqdm\nimport matplotlib.pyplot as plt\n\nimport torch\nimport torch.nn as nn\n\nwarnings.filterwarnings(\"ignore\")\nos.environ[\"OMP_NUM_THREADS\"] = \"1\"\n\n# ====================================================\n# 1. Paths & Config\n# ====================================================\nBASE_PATH = \"/kaggle/input/recodai-luc-scientific-image-forgery-detection\"\nTEST_IMG_PATH = os.path.join(BASE_PATH, \"test_images\")\n\nMODEL_PATH = \"/kaggle/input/imageforgerytrainedmodels/model_final1.pth\"\nSUBMISSION_PATH = \"submission.csv\"\n\nIMG_SIZE = 1024       # use high-resolution inference\nBATCH_SIZE = 2\nTHRESHOLD = 0.35      # can tune 0.25–0.45\nMIN_BLOB_AREA = 60    # remove tiny false positives (<60px)\n\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"✅ Using {torch.cuda.device_count()} GPU(s): {DEVICE}\")\n\n# ====================================================\n# 2. Model Definition (same as training)\n# ====================================================\nclass ConvBlock(nn.Module):\n    def __init__(self, c1, c2):\n        super().__init__()\n        self.seq = nn.Sequential(\n            nn.Conv2d(c1, c2, 3, padding=1), nn.BatchNorm2d(c2), nn.ReLU(True),\n            nn.Conv2d(c2, c2, 3, padding=1), nn.BatchNorm2d(c2), nn.ReLU(True)\n        )\n    def forward(self, x): return self.seq(x)\n\nclass UNet(nn.Module):\n    def __init__(self, in_ch=3, out_ch=1, base=32):\n        super().__init__()\n        self.enc1 = ConvBlock(in_ch, base)\n        self.enc2 = ConvBlock(base, base*2)\n        self.enc3 = ConvBlock(base*2, base*4)\n        self.enc4 = ConvBlock(base*4, base*8)\n        self.pool = nn.MaxPool2d(2)\n        self.bottleneck = ConvBlock(base*8, base*16)\n        self.up4 = nn.ConvTranspose2d(base*16, base*8, 2, stride=2)\n        self.dec4 = ConvBlock(base*16, base*8)\n        self.up3 = nn.ConvTranspose2d(base*8, base*4, 2, stride=2)\n        self.dec3 = ConvBlock(base*8, base*4)\n        self.up2 = nn.ConvTranspose2d(base*4, base*2, 2, stride=2)\n        self.dec2 = ConvBlock(base*4, base*2)\n        self.up1 = nn.ConvTranspose2d(base*2, base, 2, stride=2)\n        self.dec1 = ConvBlock(base*2, base)\n        self.out_conv = nn.Conv2d(base, out_ch, 1)\n    def forward(self, x):\n        e1 = self.enc1(x)\n        e2 = self.enc2(self.pool(e1))\n        e3 = self.enc3(self.pool(e2))\n        e4 = self.enc4(self.pool(e3))\n        b  = self.bottleneck(self.pool(e4))\n        d4 = self.dec4(torch.cat([self.up4(b), e4], 1))\n        d3 = self.dec3(torch.cat([self.up3(d4), e3], 1))\n        d2 = self.dec2(torch.cat([self.up2(d3), e2], 1))\n        d1 = self.dec1(torch.cat([self.up1(d2), e1], 1))\n        return self.out_conv(d1)\n\n# ====================================================\n# 3. Utilities\n# ====================================================\ndef read_image(path):\n    try:\n        img = plt.imread(path)\n    except:\n        img = np.zeros((512,512,3), np.uint8)\n    if img is None or img.size == 0:\n        img = np.zeros((512,512,3), np.uint8)\n    if img.ndim == 2:\n        img = np.stack([img,img,img], -1)\n    elif img.shape[2] == 4:\n        img = img[:,:,:3]\n    if img.dtype != np.uint8:\n        img = (img*255).astype(np.uint8)\n    return img\n\ndef clean_mask(mask, min_area=50):\n    mask = (mask > 0.5).astype(np.uint8)\n    num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(mask, connectivity=8)\n    cleaned = np.zeros_like(mask)\n    for i in range(1, num_labels):\n        if stats[i, cv2.CC_STAT_AREA] >= min_area:\n            cleaned[labels == i] = 1\n    return cleaned\n\ndef rle_encode(mask):\n    pixels = mask.T.flatten()\n    dots = np.where(pixels == 1)[0]\n    run_lengths, prev = [], -2\n    for b in dots:\n        if b > prev + 1: run_lengths.extend((b + 1, 0))\n        run_lengths[-1] += 1\n        prev = b\n    return json.dumps(run_lengths)\n\ndef mask_to_rle(mask, threshold=0.5):\n    mask = (mask > threshold).astype(np.uint8)\n    if mask.sum() == 0:\n        return \"authentic\"\n    return rle_encode(mask)\n\n# ====================================================\n# 4. Load Model\n# ====================================================\nmodel = UNet(base=32).to(DEVICE)\nif torch.cuda.device_count() > 1:\n    model = nn.DataParallel(model)\nstate = torch.load(MODEL_PATH, map_location=DEVICE)\nmodel.load_state_dict(state)\nmodel.eval()\nprint(\"✅ Model loaded successfully\")\n\n# ====================================================\n# 5. Tile-based Inference Function\n# ====================================================\ndef predict_tiled(image, model, tile_size=512, overlap=64):\n    \"\"\"Split large image into overlapping tiles, predict each, then stitch back.\"\"\"\n    h, w = image.shape[:2]\n    stride = tile_size - overlap\n    full_pred = np.zeros((h, w), dtype=np.float32)\n    weight_map = np.zeros((h, w), dtype=np.float32)\n\n    tiles = []\n    coords = []\n    for y in range(0, h, stride):\n        for x in range(0, w, stride):\n            y2 = min(y + tile_size, h)\n            x2 = min(x + tile_size, w)\n            tile = image[y:y2, x:x2]\n            pad_y, pad_x = tile_size - tile.shape[0], tile_size - tile.shape[1]\n            tile = cv2.copyMakeBorder(tile, 0, pad_y, 0, pad_x, cv2.BORDER_REFLECT)\n            tiles.append(tile)\n            coords.append((y, y2, x, x2))\n\n    preds = []\n    with torch.no_grad(), torch.amp.autocast(\"cuda\"):\n        for i in range(0, len(tiles), BATCH_SIZE):\n            batch = tiles[i:i+BATCH_SIZE]\n            batch_t = [torch.tensor(t.transpose(2,0,1), dtype=torch.float32)/255. for t in batch]\n            batch_t = torch.stack(batch_t).to(DEVICE)\n            p = torch.sigmoid(model(batch_t)).cpu().numpy()\n            preds.extend(p)\n\n    # stitch predictions\n    for (y, y2, x, x2), p in zip(coords, preds):\n        p = p.squeeze()\n        p = p[:(y2-y), :(x2-x)]\n        full_pred[y:y2, x:x2] += p\n        weight_map[y:y2, x:x2] += 1.0\n    weight_map[weight_map == 0] = 1\n    full_pred /= weight_map\n    return full_pred\n\n# ====================================================\n# 6. Run Inference on Test Set\n# ====================================================\ntest_images = sorted(glob(os.path.join(TEST_IMG_PATH, \"*.png\")))\nprint(f\"Found {len(test_images)} test images.\")\n\npredictions = []\n\nfor path in tqdm(test_images, desc=\"Predicting\"):\n    img = read_image(path)\n    pred = predict_tiled(img, model, tile_size=512, overlap=64)\n    pred = clean_mask(pred, min_area=MIN_BLOB_AREA)\n    rle = mask_to_rle(pred, threshold=THRESHOLD)\n    case_id = os.path.splitext(os.path.basename(path))[0]\n    predictions.append({\"case_id\": case_id, \"annotation\": rle})\n    gc.collect()\n\ndf_sub = pd.DataFrame(predictions)\ndf_sub.to_csv(SUBMISSION_PATH, index=False)\nprint(f\"\\n✅ Submission file saved: {SUBMISSION_PATH}\")\ndisplay(df_sub.head())\n\n# ====================================================\n# 7. Optional Visualization\n# ====================================================\nfor i in range(min(3, len(test_images))):\n    path = test_images[i]\n    img = read_image(path)\n    pred = predict_tiled(img, model)\n    pred = clean_mask(pred, min_area=MIN_BLOB_AREA)\n    plt.figure(figsize=(10,4))\n    plt.subplot(1,2,1); plt.imshow(img); plt.title(os.path.basename(path))\n    plt.subplot(1,2,2); plt.imshow(pred>THRESHOLD); plt.title(\"Predicted Forgery Mask\")\n    plt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-25T06:56:09.419960Z","iopub.execute_input":"2025-10-25T06:56:09.420553Z","iopub.status.idle":"2025-10-25T06:56:12.458067Z","shell.execute_reply.started":"2025-10-25T06:56:09.420527Z","shell.execute_reply":"2025-10-25T06:56:12.457371Z"}},"outputs":[],"execution_count":null}]}