{"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":14456136,"sourceType":"competition"},{"sourceId":13758049,"sourceType":"datasetVersion","datasetId":8670261}],"dockerImageVersionId":31193,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"#### AQUI\nimport cv2\nimport matplotlib.pyplot as plt\nimport numpy as np\n\n# Load the image\nimg_path = \"/kaggle/input/recodai-luc-scientific-image-forgery-detection/test_images/45.png\"\nimg = cv2.cvtColor(cv2.imread(img_path), cv2.COLOR_BGR2RGB)\n\nplt.figure(figsize=(10, 8))\nplt.imshow(img)\nplt.title(\"TEST IMAGE: 45.png — LOOK FOR FORGERY\", fontsize=16)\nplt.axis('off')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-06T04:58:44.057404Z","iopub.execute_input":"2025-12-06T04:58:44.057932Z","iopub.status.idle":"2025-12-06T04:58:44.929116Z","shell.execute_reply.started":"2025-12-06T04:58:44.057904Z","shell.execute_reply":"2025-12-06T04:58:44.928433Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport cv2\nimport os\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport pandas as pd\nfrom tqdm.auto import tqdm\nimport csv\nimport gc \n\n# =========================================================\n# CONFIG — HYPERPARAMETERS (New Test: TH=0.30)\n# =========================================================\nSZ, BS = 256, 32\nDEV = 'cuda'\n# New Threshold for testing\nTH = 0.30 \nMA = 16 \n\n# PATHS\nBASE = \"/kaggle/input/recodai-luc-scientific-image-forgery-detection\"\nMODEL = \"/kaggle/input/rluc-sfic-st/recodai_model.pth\"\nOUT = \"/kaggle/working/submission_th_030.csv\" # Specific name for this test\nTEST = f\"{BASE}/test_images\"\nSAMP = f\"{BASE}/sample_submission.csv\"\n\n# =========================================================\n# 1. OPTIMIZED PREPROCESSING FUNCTIONS (Omitted for brevity, assumed identical)\n# =========================================================\n# (Includes standard ELA and load_img functions)\n\ndef ela(p, img_data):\n    if p.endswith('.npy'): \n        return np.zeros((SZ,SZ), np.float32)\n\n    _, b = cv2.imencode('.jpg', img_data, [cv2.IMWRITE_JPEG_QUALITY, 90])\n    c = cv2.imdecode(b, cv2.IMREAD_COLOR)\n    \n    if img_data.shape[2] == 3 and c.shape[2] == 3:\n        c_rgb = cv2.cvtColor(c, cv2.COLOR_BGR2RGB)\n    else:\n        c_rgb = c \n\n    return (np.abs(img_data.astype(np.float32) - c_rgb.astype(np.float32)).mean(axis=2) * 8).astype(np.float32)\n\ndef load_img(p):\n    try:\n        if p.endswith('.npy'):\n            d = np.load(p)\n            if d.ndim == 2: d = cv2.cvtColor(d, cv2.COLOR_GRAY2RGB)\n            elif d.ndim == 3 and d.shape[2] == 1: d = np.repeat(d, 3, axis=2)\n            return cv2.resize(d.astype('uint8'), (SZ,SZ), interpolation=cv2.INTER_AREA)\n        \n        img = cv2.imread(p, cv2.IMREAD_COLOR)\n        if img is None: raise ValueError()\n        \n        img_resized = cv2.resize(img, (SZ,SZ), interpolation=cv2.INTER_AREA)\n        return cv2.cvtColor(img_resized, cv2.COLOR_BGR2RGB)\n    except: \n        return np.zeros((SZ,SZ,3), 'uint8')\n\ndef get_df():\n    df = pd.read_csv(SAMP)\n    df['case_id'] = df['case_id'].astype(str)\n    \n    path_map = {}\n    for r, _, f in os.walk(TEST):\n        for x in f:\n            ext = x.lower().split('.')[-1]\n            if ext in ['png','jpg','jpeg','tif','tiff','npy']:\n                path_map[os.path.splitext(x)[0]] = os.path.join(r, x)\n    \n    df['p'] = df['case_id'].map(path_map).fillna('MISSING')\n    \n    data_list = []\n    \n    for i, row in tqdm(df.iterrows(), total=len(df), desc=\"Preprocessing\"):\n        p = row['p']\n        if p == 'MISSING':\n            data_list.append((None, None))\n            continue\n            \n        rgb = load_img(p)\n        e = ela(p, rgb)\n        \n        x = np.concatenate([rgb, e[..., None]], axis=-1).astype(np.float32) / 255.0\n        data_list.append((rgb.shape[:2], x))\n        \n    df[['orig_sz', 'input_data']] = pd.DataFrame(data_list, index=df.index)\n    \n    return df[['case_id', 'p', 'orig_sz', 'input_data']]\n\n\n# =========================================================\n# 2. MODEL DEFINITION (UNet)\n# =========================================================\nclass UNet(nn.Module):\n    def __init__(self):\n        super().__init__()\n        def conv(i,o):\n            return nn.Sequential(\n                nn.Conv2d(i,o,3,1,1), nn.BatchNorm2d(o), nn.ReLU(), nn.Dropout(0.5),\n                nn.Conv2d(o,o,3,1,1), nn.BatchNorm2d(o), nn.ReLU()\n            )\n        self.e1 = conv(4,64)\n        self.e2 = conv(64,128)\n        self.bot = conv(128,256)\n        self.up2 = nn.ConvTranspose2d(256,128,2,2)   \n        self.d2 = conv(256,128)\n        self.up1 = nn.ConvTranspose2d(128,64,2,2)    \n        self.d1 = conv(128,64)\n        self.out = nn.Conv2d(64,1,1)                 \n        \n    def forward(self,x):\n        e1 = self.e1(x)\n        e2 = self.e2(F.max_pool2d(e1,2))\n        bot = self.bot(F.max_pool2d(e2,2))\n        d2 = self.up2(bot)\n        d2 = torch.cat([d2, e2], dim=1) \n        d2 = self.d2(d2)\n        d1 = self.up1(d2)\n        d1 = torch.cat([d1, e1], dim=1) \n        d1 = self.d1(d1)\n        return torch.sigmoid(self.out(d1))\n\n# =========================================================\n# 3. RLE ENCODING & INFERENCE\n# =========================================================\ndef rle_encode(mask):\n    if mask.sum() == 0:\n        return \"authentic\"\n    pixels = mask.flatten(order='C')\n    pixels = np.concatenate([[0], pixels, [0]])\n    runs = []\n    prev = 0\n    for i in range(1, len(pixels)):\n        if pixels[i] != pixels[i-1]:\n            length = i - prev\n            if pixels[prev] == 1 and length > 0:\n                runs.extend([prev + 1, length]) \n            prev = i\n    \n    length = len(pixels) - prev\n    if pixels[prev] == 1 and length > 0:\n        runs.extend([prev + 1, length])\n\n    if len(runs) % 2 == 1:\n        runs.append(0)\n        \n    return f\"[{','.join(map(str, runs))}]\"\n\n@torch.no_grad()\ndef infer(model, df, th=TH, ma=MA):\n    model.eval()\n    results = []\n    batch_data = [] \n    \n    for i, row in tqdm(df.iterrows(), total=len(df), desc=\"Inference\"):\n        cid, p, orig_sz, input_data = row['case_id'], row['p'], row['orig_sz'], row['input_data']\n        \n        # --- MEMORY MANAGEMENT ---\n        if i > 0 and i % 100 == 0: \n            gc.collect()\n            torch.cuda.empty_cache()\n        # -------------------------\n\n        if p == 'MISSING':\n            results.append({'case_id': cid, 'annotation': 'authentic'})\n            continue\n        \n        batch_data.append((cid, orig_sz, input_data))\n        \n        if len(batch_data) == BS or i == len(df) - 1:\n            \n            # --- GPU Inference ---\n            inputs = torch.stack([torch.from_numpy(d[2].transpose(2,0,1)) for d in batch_data]).to(DEV)\n            flip = torch.flip(inputs, [3])\n            prob = (model(inputs) + torch.flip(model(flip), [3])) / 2\n            prob = prob.cpu().numpy()\n            \n            for j, (cid_batch, orig_sz_batch, _) in enumerate(batch_data):\n                \n                # Thresholding\n                mask = (prob[j].squeeze() > th).astype('uint8')\n                \n                # --- Post-Processing (Connected Components) ---\n                mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, np.ones((3,3), 'uint8'))\n                num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(mask, 4, cv2.CV_32S)\n                clean = np.zeros_like(mask, dtype=np.uint8)\n                \n                # Area Filtering\n                for k in range(1, num_labels):\n                    if stats[k, cv2.CC_STAT_AREA] >= ma: \n                        clean[labels == k] = 1\n                \n                # Resize and Encode\n                final = cv2.resize(clean, (orig_sz_batch[1], orig_sz_batch[0]), interpolation=cv2.INTER_NEAREST)\n                rle = rle_encode(final)\n                \n                results.append({'case_id': cid_batch, 'annotation': rle})\n            \n            del inputs, flip, prob\n            batch_data.clear()\n            \n    return pd.DataFrame(results)\n\n# =========================================================\n# 4. MAIN EXECUTION (Generate Submission with TH=0.30)\n# =========================================================\nif __name__ == \"__main__\":\n    # 1. Load Model\n    model = UNet().to(DEV)\n    state = torch.load(MODEL, map_location=DEV)\n    \n    # CRITICAL CORRECTION: Rename keys\n    keys_to_pop = ['up2.weight', 'up2.bias', 'up1.weight', 'up1.bias', 'out.weight', 'out.bias']\n    for key in keys_to_pop:\n        state[key] = state.pop(key)\n    model.load_state_dict(state)\n    \n    # 2. Prepare Data \n    df = get_df()\n    \n    # 3. Perform Inference with TH=0.30\n    res = infer(model, df)\n    \n    # 4. Merge and Create Final DataFrame \n    sub = df[['case_id']].merge(res, on='case_id', how='left').fillna('authentic')\n    \n    # 5. Save the final submission CSV\n    with open(OUT, 'w', newline='') as f:\n        writer = csv.writer(f, quoting=csv.QUOTE_MINIMAL)\n        writer.writerow(['case_id', 'annotation'])\n        for _, r in sub.iterrows():\n            writer.writerow([str(r['case_id']), r['annotation']])\n    print(f\"SUBMISSION READY: {OUT}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-06T05:42:27.335230Z","iopub.execute_input":"2025-12-06T05:42:27.336063Z","iopub.status.idle":"2025-12-06T05:42:27.540025Z","shell.execute_reply.started":"2025-12-06T05:42:27.336034Z","shell.execute_reply":"2025-12-06T05:42:27.539062Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!mv /kaggle/working/submission_th_030.csv /kaggle/working/submission.csv","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-06T05:42:44.305173Z","iopub.execute_input":"2025-12-06T05:42:44.305503Z","iopub.status.idle":"2025-12-06T05:42:44.442213Z","shell.execute_reply.started":"2025-12-06T05:42:44.305472Z","shell.execute_reply":"2025-12-06T05:42:44.441468Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!cat /kaggle/working/submission.csv","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-06T05:42:51.975866Z","iopub.execute_input":"2025-12-06T05:42:51.976438Z","iopub.status.idle":"2025-12-06T05:42:52.112566Z","shell.execute_reply.started":"2025-12-06T05:42:51.976405Z","shell.execute_reply":"2025-12-06T05:42:52.111885Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import cv2\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport pandas as pd\n\n# ------------------- PATHS -------------------\nIMG_PATH = \"/kaggle/input/recodai-luc-scientific-image-forgery-detection/test_images/45.png\"\nSUBMISSION_CSV = \"submission.csv\"\n\n# ------------------- LOAD IMAGE -------------------\nimg = cv2.cvtColor(cv2.imread(IMG_PATH), cv2.COLOR_BGR2RGB)\nh, w = img.shape[:2]\n\n# ------------------- LOAD RLE FROM SUBMISSION -------------------\ndf = pd.read_csv(SUBMISSION_CSV)\ndf['case_id'] = df['case_id'].astype(str)\n\nprint(\"Available case_ids (as str):\", df['case_id'].tolist()[:10])\n\n# Search for '45' or '45.png'\nrow = df[df['case_id'] == '45']\nif len(row) == 0:\n    row = df[df['case_id'] == '45.png']\nif len(row) == 0:\n    raise ValueError(\"case_id '45' or '45.png' not found in submission.csv\")\n\nrle_str = row.iloc[0]['annotation']\ncase_id = row.iloc[0]['case_id']\nprint(f\"Found: case_id = {case_id} → RLE loaded\")\n\n# ------------------- DECODE RLE -------------------\ndef rle_decode(rle_str, shape):\n    if rle_str == 'authentic' or not rle_str.startswith('['):\n        return np.zeros(shape, dtype=np.uint8)\n    rle_str = rle_str.strip('[]')\n    if not rle_str.strip():\n        return np.zeros(shape, dtype=np.uint8)\n    numbers = [int(x) for x in rle_str.split(',') if x.strip()]\n    if len(numbers) % 2 != 0:\n        print(\"Warning: Odd number of RLE values!\")\n        return np.zeros(shape, dtype=np.uint8)\n    mask = np.zeros(shape[0] * shape[1], dtype=np.uint8)\n    for i in range(0, len(numbers), 2):\n        start = numbers[i] - 1\n        length = numbers[i + 1]\n        if start + length <= len(mask):\n            mask[start:start + length] = 1\n    # Note: RLE is typically column-major, thus the transpose and reshape logic is preserved\n    return mask.reshape(shape[1], shape[0]).T  # (H, W)\n\nmask = rle_decode(rle_str, (h, w))\n\n# ------------------- CALCULATE DYNAMIC STATS -------------------\npairs = len(rle_str.strip('[]').split(',')) // 2 if '[' in rle_str else 0\nforged_pixels = mask.sum()\nforged_pct = forged_pixels / (h * w) * 100\n\n# =========================================================\n# CORRECTED VISUALIZATION BLOCK\n# Changes: \n# 1. Increased figure size and added a 4th subplot for zoom.\n# 2. Used a brighter overlay (alpha=1.0) for the forged area.\n# 3. Calculated a central forged point to focus the zoom window.\n# =========================================================\nfig, axes = plt.subplots(1, 4, figsize=(28, 7))\n\n# --- Subplot 1: Original Image ---\naxes[0].imshow(img)\naxes[0].set_title(\"Original: 45.png\", fontsize=16)\naxes[0].axis('off')\n\n# --- Subplot 2: Binary Mask ---\naxes[1].imshow(mask, cmap='gray')\naxes[1].set_title(f\"Binary Mask ({pairs} RLE Segments)\", fontsize=16)\naxes[1].axis('off')\n\n# --- Subplot 3: Forgery Overlaid (Full Image) ---\noverlay = img.copy()\n# Use a strong red color for visibility\noverlay[mask == 1] = [255, 0, 0] \n# Use a much stronger blend (alpha=1.0) to ensure the 724 pixels are not invisible\n# We will use the mask directly here for maximum visibility\nblended = img.copy()\nblended[mask == 1] = [255, 0, 0] # Pure red on forged pixels\n\naxes[2].imshow(blended)\naxes[2].set_title(\"Forgery Detected (Max Visibility)\", fontsize=16)\naxes[2].axis('off')\n\n# --- Subplot 4: Zoomed View (CRITICAL FOR VISIBILITY) ---\nif forged_pixels > 0:\n    # Find the central coordinates of the detected forgery\n    y_idx, x_idx = np.where(mask == 1)\n    center_y, center_x = y_idx[len(y_idx)//2], x_idx[len(x_idx)//2]\n    \n    # Define a small crop window around the center\n    zoom_w = 100 # Zoom window width/height\n    min_x = max(0, center_x - zoom_w // 2)\n    max_x = min(w, center_x + zoom_w // 2)\n    min_y = max(0, center_y - zoom_w // 2)\n    max_y = min(h, center_y + zoom_w // 2)\n\n    # Apply the crop and display\n    zoomed_blended = blended[min_y:max_y, min_x:max_x]\n    \n    axes[3].imshow(zoomed_blended)\n    axes[3].set_title(f\"Zoomed View (Center at: {center_x},{center_y})\", fontsize=16)\n    axes[3].axis('on')\n    # Add a marker to show the exact center of the forgery cluster\n    axes[3].scatter(center_x - min_x, center_y - min_y, color='yellow', s=50, marker='x')\n\nelse:\n    axes[3].set_title(\"Zoomed View (No Forgery)\", fontsize=16)\n    axes[3].imshow(np.zeros((h, w, 3), dtype=np.uint8))\n    axes[3].axis('off')\n    \n\nplt.suptitle(f\"RLE VISUALIZATION: {pairs} Forgery Segments ({forged_pixels} Pixels)\", fontsize=18, y=0.95)\nplt.tight_layout()\nplt.show()\n\n# ------------------- DYNAMIC STATS -------------------\nprint(f\"\\nRLE STATS:\")\nprint(f\"   case_id: {case_id}\")\nprint(f\"   RLE (first 100): {rle_str[:100]}{'...' if len(rle_str) > 100 else ''}\")\nprint(f\"   Total RLE Pairs: {pairs}\")\nprint(f\"   Forged Pixels: {forged_pixels} ({forged_pct:.3f}% of image)\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-06T05:43:39.220340Z","iopub.execute_input":"2025-12-06T05:43:39.220625Z","iopub.status.idle":"2025-12-06T05:43:40.750604Z","shell.execute_reply.started":"2025-12-06T05:43:39.220599Z","shell.execute_reply":"2025-12-06T05:43:40.749659Z"}},"outputs":[],"execution_count":null}]}