{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.11.13"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":113558,"databundleVersionId":14456136,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":4534,"sourceType":"modelInstanceVersion","isSourceIdPinned":false,"modelInstanceId":3326,"modelId":986},{"sourceId":648498,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":489174,"modelId":504592}],"dockerImageVersionId":31193,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true},"papermill":{"default_parameters":{},"duration":51.085353,"end_time":"2025-11-16T16:15:41.162948","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2025-11-16T16:14:50.077595","version":"2.6.0"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Imports and configs","metadata":{}},{"cell_type":"code","source":"!pip uninstall -qy tensorflow","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from transformers import AutoImageProcessor, AutoModel\nfrom torch.utils.data import Dataset\nfrom tqdm.notebook import tqdm\nfrom pathlib import Path\nfrom PIL import Image\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport pandas as pd\nimport numpy as np\nimport warnings\nimport torch\nimport json\nimport math\nimport cv2\nimport os\n\nwarnings.filterwarnings(\"ignore\")","metadata":{"trusted":true,"_kg_hide-output":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CFG:\n    test_images_path = \"/kaggle/input/recodai-luc-scientific-image-forgery-detection/test_images\"\n    sample_sub_path = \"/kaggle/input/recodai-luc-scientific-image-forgery-detection/sample_submission.csv\"\n\n    dino_path = \"/kaggle/input/dinov2/pytorch/base/1\"\n    dino_weights_path = \"/kaggle/input/m/ravaghi/dinov2/pytorch/base/1/model.pt\"\n\n    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n    img_size = 512\n    \n    use_tta = True","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Model","metadata":{}},{"cell_type":"code","source":"class DinoTinyDecoder(nn.Module):\n    def __init__(self, in_ch=768, out_ch=1):\n        super().__init__()\n        self.net = nn.Sequential(\n            nn.Conv2d(in_ch, 256, 3, padding=1), nn.ReLU(),\n            nn.Conv2d(256, 64, 3, padding=1), nn.ReLU(),\n            nn.Conv2d(64, out_ch, 1)\n        )\n\n    def forward(self, f, size):\n        return self.net(F.interpolate(f, size=size, mode=\"bilinear\", align_corners=False))\n\n\nclass DinoSegmenter(nn.Module):\n    def __init__(self, encoder, processor):\n        super().__init__()\n        self.encoder, self.processor = encoder, processor\n        \n        for p in self.encoder.parameters():\n            p.requires_grad = False\n        \n        self.seg_head = DinoTinyDecoder(768, 1)\n\n    def forward_features(self, x):\n        imgs = (x*255).clamp(0, 255).byte().permute(0, 2, 3, 1).cpu().numpy()\n        inputs = self.processor(images=list(imgs), return_tensors=\"pt\").to(x.device)\n        \n        with torch.no_grad():\n            feats = self.encoder(**inputs).last_hidden_state\n        \n        B, N, C = feats.shape\n        fmap = feats[:, 1:, :].permute(0, 2, 1)\n        s = int(math.sqrt(N-1))\n        fmap = fmap.reshape(B, C, s, s)\n        \n        return fmap\n\n    def forward_seg(self, x):\n        fmap = self.forward_features(x)\n        return self.seg_head(fmap, (CFG.img_size, CFG.img_size))","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"processor = AutoImageProcessor.from_pretrained(CFG.dino_path, local_files_only=True)\nencoder = AutoModel.from_pretrained(CFG.dino_path, local_files_only=True).eval().to(CFG.device)\n\nmodel = DinoSegmenter(encoder, processor).to(CFG.device)\nmodel.load_state_dict(torch.load(CFG.dino_weights_path))\nmodel.eval() ","metadata":{"trusted":true,"_kg_hide-output":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Inference","metadata":{}},{"cell_type":"code","source":"def rle_encode(mask):\n    pixels = mask.T.flatten()\n    dots = np.where(pixels == 1)[0]\n    \n    if len(dots) == 0:\n        return \"authentic\"\n    \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    \n    return json.dumps([int(x) for x in run_lengths])","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"@torch.no_grad()\ndef predict_with_tta(model, image):\n    predictions = []\n\n    pred = torch.sigmoid(model.forward_seg(image))\n    predictions.append(pred)\n\n    pred = torch.sigmoid(model.forward_seg(torch.flip(image, dims=[3])))\n    predictions.append(torch.flip(pred, dims=[3]))\n\n    pred = torch.sigmoid(model.forward_seg(torch.flip(image, dims=[2])))\n    predictions.append(torch.flip(pred, dims=[2]))\n\n    return torch.stack(predictions).mean(0)[0, 0].cpu().numpy()\n\n\n@torch.no_grad()\ndef predict(model, image):\n    return torch.sigmoid(model.forward_seg(image))[0,0].cpu().numpy()\n\n\ndef postprocess(preds, original_size, alpha_grad=0.35):\n    gx = cv2.Sobel(preds, cv2.CV_32F, 1, 0, ksize=3)\n    gy = cv2.Sobel(preds, cv2.CV_32F, 0, 1, ksize=3)\n    grad_mag = np.sqrt(gx**2 + gy**2)\n    grad_norm = grad_mag / (grad_mag.max() + 1e-6)\n    enhanced = (1 - alpha_grad) * preds + alpha_grad * grad_norm\n    enhanced = cv2.GaussianBlur(enhanced, (3, 3), 0)\n    thr = np.mean(enhanced) + 0.3 * np.std(enhanced)\n    mask = (enhanced > thr).astype(np.uint8)\n    mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, np.ones((5, 5), np.uint8))\n    mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, np.ones((3, 3), np.uint8))\n    \n    mask = cv2.resize(mask, original_size, interpolation=cv2.INTER_NEAREST)\n    \n    return mask\n\n\ndef infer_image(image):\n    image_array = np.array(image.resize((CFG.img_size, CFG.img_size)), np.float32) / 255\n    image_array = torch.from_numpy(image_array).permute(2, 0, 1)[None].to(CFG.device)\n    \n    if CFG.use_tta:\n        preds = predict_with_tta(model, image_array)\n    else:\n        preds = predict(model, image_array)\n    \n    mask = postprocess(preds, image.size)\n    \n    area = int(mask.sum())\n    if area > 0:\n        mean_inside = float(preds[cv2.resize(mask, (CFG.img_size, CFG.img_size), interpolation=cv2.INTER_NEAREST) == 1].mean())\n    else:\n        mean_inside = 0.0\n\n    if area < 400 or mean_inside < 0.3:\n        return \"authentic\", None    \n    \n    return \"forged\", mask","metadata":{"papermill":{"duration":41.966375,"end_time":"2025-11-16T16:15:35.879746","exception":false,"start_time":"2025-11-16T16:14:53.913371","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"predictions = []\n\nfor image_path in tqdm(sorted(os.listdir(CFG.test_images_path)), desc=\"Running Inference\"):\n    image = Image.open(Path(CFG.test_images_path)/image_path).convert(\"RGB\")\n    label, mask = infer_image(image)\n\n    if mask is None:\n        mask = np.zeros(image.size[::-1], np.uint8)\n    else:\n        mask = np.array(mask, dtype=np.uint8)\n\n    if label == \"authentic\":\n        annotation = \"authentic\"\n    else:\n        annotation = rle_encode((mask > 0).astype(np.uint8))\n\n    predictions.append({\n        \"case_id\": Path(image_path).stem,\n        \"annotation\": annotation\n    })","metadata":{"papermill":{"duration":1.730411,"end_time":"2025-11-16T16:15:37.612052","exception":false,"start_time":"2025-11-16T16:15:35.881641","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Submission","metadata":{}},{"cell_type":"code","source":"predictions = pd.DataFrame(predictions)\npredictions[\"case_id\"] = predictions[\"case_id\"].astype(str)\n\nsubmission = pd.read_csv(CFG.sample_sub_path)\nsubmission[\"case_id\"] = submission[\"case_id\"].astype(str)\n\nsubmission = submission[[\"case_id\"]].merge(predictions, on=\"case_id\", how=\"left\")\nsubmission[\"annotation\"] = submission[\"annotation\"].fillna(\"authentic\")\nsubmission[[\"case_id\", \"annotation\"]].to_csv(\"submission.csv\", index=False)\nsubmission.head()","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}