{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":113558,"databundleVersionId":14878066,"sourceType":"competition"}],"dockerImageVersionId":31234,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## IMPORTING MODULES","metadata":{}},{"cell_type":"code","source":"import os\nimport numpy as np\nimport cv2\nimport matplotlib.pyplot as plt\nfrom glob import glob\nfrom skimage.measure import label, regionprops\nimport seaborn as sns\nimport pandas as pd\nimport random\nfrom skimage import exposure  \n\n\nsns.set(style='whitegrid')","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-01-06T19:39:39.175788Z","iopub.execute_input":"2026-01-06T19:39:39.176916Z","iopub.status.idle":"2026-01-06T19:39:41.158790Z","shell.execute_reply.started":"2026-01-06T19:39:39.176845Z","shell.execute_reply":"2026-01-06T19:39:41.157535Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":" ## EDA START","metadata":{}},{"cell_type":"code","source":"import os\nfrom glob import glob\n\nBASE_DIR = \"/kaggle/input/recodai-luc-scientific-image-forgery-detection\"\n\ntrain_image_paths = sorted(\n    glob(os.path.join(BASE_DIR, \"train_images\", \"*\", \"*.png\"))\n)\n\ntrain_mask_paths = sorted(\n    glob(os.path.join(BASE_DIR, \"train_masks\", \"*.npy\"))\n)\n\ntest_image_paths = sorted(\n    glob(os.path.join(BASE_DIR, \"test_images\", \"*\", \"*.png\"))\n)\n\nprint(f\"Train images: {len(train_image_paths)}, Train masks: {len(train_mask_paths)}\")\nprint(f\"Test images: {len(test_image_paths)}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-06T19:39:41.161131Z","iopub.execute_input":"2026-01-06T19:39:41.161648Z","iopub.status.idle":"2026-01-06T19:39:41.285054Z","shell.execute_reply.started":"2026-01-06T19:39:41.161614Z","shell.execute_reply":"2026-01-06T19:39:41.284207Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def get_id(path):\n    return os.path.splitext(os.path.basename(path))[0]\n\nmask_map = {get_id(p): p for p in train_mask_paths}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-06T19:39:41.286231Z","iopub.execute_input":"2026-01-06T19:39:41.286650Z","iopub.status.idle":"2026-01-06T19:39:41.298724Z","shell.execute_reply.started":"2026-01-06T19:39:41.286621Z","shell.execute_reply":"2026-01-06T19:39:41.297330Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pairs = []\nfor img_path in train_image_paths:\n    cid = get_id(img_path)\n    if cid in mask_map:\n        mask_path = mask_map[cid]\n        has_forgery = True\n    else:\n        mask_path = None\n        has_forgery = False\n    pairs.append({\"img_path\": img_path, \"mask_path\": mask_path, \"has_forgery\": has_forgery})","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-06T19:39:41.301051Z","iopub.execute_input":"2026-01-06T19:39:41.301559Z","iopub.status.idle":"2026-01-06T19:39:41.331320Z","shell.execute_reply.started":"2026-01-06T19:39:41.301528Z","shell.execute_reply":"2026-01-06T19:39:41.330049Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"total_train = len(pairs)\nforged_count = sum(p['has_forgery'] for p in pairs)\nauthentic_count = total_train - forged_count\nauth_ratio = authentic_count / total_train if total_train > 0 else 0\n\nprint(f\"Total train images: {total_train}\")\nprint(f\"Forged images: {forged_count} ({forged_count / total_train:.2%})\")\nprint(f\"Authentic images: {authentic_count} ({auth_ratio:.2%})\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-06T19:39:41.332426Z","iopub.execute_input":"2026-01-06T19:39:41.332795Z","iopub.status.idle":"2026-01-06T19:39:41.351874Z","shell.execute_reply.started":"2026-01-06T19:39:41.332768Z","shell.execute_reply":"2026-01-06T19:39:41.350911Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nlabels = ['Forged', 'Authentic']\nsizes = [forged_count, authentic_count]\ncolors = ['#ff9999', '#66b3ff']\nplt.figure(figsize=(6, 6))\nplt.pie(sizes, labels=labels, colors=colors, autopct='%1.1f%%', startangle=90)\nplt.title('Class Distribution in Train Set')\nplt.axis('equal')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-06T19:39:41.353499Z","iopub.execute_input":"2026-01-06T19:39:41.353914Z","iopub.status.idle":"2026-01-06T19:39:41.543338Z","shell.execute_reply.started":"2026-01-06T19:39:41.353885Z","shell.execute_reply":"2026-01-06T19:39:41.542023Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_mask(mask_path, img_shape):\n    H, W = img_shape[:2]\n    if H == 0 or W == 0:\n        return np.zeros((0, 0), dtype=np.uint8)\n    if mask_path is None:\n        return np.zeros((H, W), dtype=np.uint8)\n    try:\n        mask = np.load(mask_path)\n    except Exception as e:\n        print(f\"Error loading mask {mask_path}: {e}\")\n        return np.zeros((H, W), dtype=np.uint8)\n    if mask.size == 0:\n        return np.zeros((H, W), dtype=np.uint8)\n    if mask.ndim == 1 or mask.size != H * W:\n        return np.zeros((H, W), dtype=np.uint8)\n    else:\n        try:\n            mask = mask.reshape((H, W))\n        except:\n            return np.zeros((H, W), dtype=np.uint8)\n    mask = (mask > 0).astype(np.uint8)\n    return mask","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-06T19:39:41.545456Z","iopub.execute_input":"2026-01-06T19:39:41.545890Z","iopub.status.idle":"2026-01-06T19:39:41.556058Z","shell.execute_reply.started":"2026-01-06T19:39:41.545844Z","shell.execute_reply":"2026-01-06T19:39:41.554837Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import cv2\nimport numpy as np\nimport pandas as pd\nfrom skimage.measure import label, regionprops\n\nfg_ratios = []\nn_components = []\nimage_shapes = []\nis_grayscale = []    \nmean_intensity = []   \n\nfor p in pairs:\n    img = cv2.imread(p[\"img_path\"])\n    if img is None or img.shape[0] == 0 or img.shape[1] == 0:\n        continue\n    image_shapes.append(img.shape)\n    \n  \n    if len(img.shape) == 2 or np.all(img[:,:,0] == img[:,:,1]) and np.all(img[:,:,1] == img[:,:,2]):\n        is_grayscale.append(True)\n        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n    else:\n        is_grayscale.append(False)\n        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n    \n\n    mean_intensity.append(np.mean(gray))\n    \n    if p[\"has_forgery\"]:\n        mask = load_mask(p[\"mask_path\"], img.shape)\n        fg_ratios.append(mask.mean())\n        labeled = label(mask)\n        props = regionprops(labeled)\n        n_components.append(len(props))\n\n\ndf_stats = pd.DataFrame({\n    'Foreground Ratio': fg_ratios,\n    'Num Components': n_components\n})\nprint(\"\\nSummary Stats for Forged Images:\")\nprint(df_stats.describe())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-06T19:39:41.557279Z","iopub.execute_input":"2026-01-06T19:39:41.557584Z","iopub.status.idle":"2026-01-06T19:46:08.512957Z","shell.execute_reply.started":"2026-01-06T19:39:41.557558Z","shell.execute_reply":"2026-01-06T19:46:08.511820Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## DATA VISUALIZATION","metadata":{}},{"cell_type":"code","source":"import seaborn as sns\nplt.figure(figsize=(12, 5))\nsns.histplot(fg_ratios, bins=50, kde=True, color='red')\nplt.title(\"Distribution of Foreground Ratios in Forged Images\")\nplt.xlabel(\"Foreground Ratio\")\nplt.ylabel(\"Count\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-06T19:46:08.514400Z","iopub.execute_input":"2026-01-06T19:46:08.514792Z","iopub.status.idle":"2026-01-06T19:46:08.823073Z","shell.execute_reply.started":"2026-01-06T19:46:08.514740Z","shell.execute_reply":"2026-01-06T19:46:08.821849Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#  num components\nplt.figure(figsize=(12, 5))\nsns.histplot(n_components, bins=20, kde=True, color='blue')\nplt.title(\"Distribution of Number of Connected Components in Forged Images\")\nplt.xlabel(\"Number of Components\")\nplt.ylabel(\"Count\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-06T19:46:08.825968Z","iopub.execute_input":"2026-01-06T19:46:08.826357Z","iopub.status.idle":"2026-01-06T19:46:09.068034Z","shell.execute_reply.started":"2026-01-06T19:46:08.826329Z","shell.execute_reply":"2026-01-06T19:46:09.066714Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Scatter plot\nplt.figure(figsize=(10, 6))\nsns.scatterplot(x=fg_ratios, y=n_components, alpha=0.6, color='purple')\nplt.xlabel(\"Foreground Ratio\")\nplt.ylabel(\"Number of Components\")\nplt.title(\"Forged Images: Foreground Ratio vs Number of Components\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-06T19:46:09.069526Z","iopub.execute_input":"2026-01-06T19:46:09.069906Z","iopub.status.idle":"2026-01-06T19:46:09.259399Z","shell.execute_reply.started":"2026-01-06T19:46:09.069877Z","shell.execute_reply":"2026-01-06T19:46:09.258268Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def get_shapes(paths):\n    shapes = []\n    for path in paths:\n        img = cv2.imread(path)\n        if img is not None:\n            shapes.append(img.shape[:2])  # (H, W)\n    return shapes\n\ntrain_shapes = get_shapes(train_image_paths)\ntest_shapes = get_shapes(test_image_paths)\n\n\ndf_train_shapes = pd.DataFrame(train_shapes, columns=['Height', 'Width'])\ndf_test_shapes = pd.DataFrame(test_shapes, columns=['Height', 'Width'])\n\nprint(\"\\nTrain Image Shapes Summary:\")\nprint(df_train_shapes.describe())\nprint(\"\\nTest Image Shapes Summary:\")\nprint(df_test_shapes.describe())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-06T19:46:09.260652Z","iopub.execute_input":"2026-01-06T19:46:09.261045Z","iopub.status.idle":"2026-01-06T19:49:30.501117Z","shell.execute_reply.started":"2026-01-06T19:46:09.261003Z","shell.execute_reply":"2026-01-06T19:49:30.500175Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(12, 5))\nsns.histplot(df_train_shapes['Height'], bins=30, kde=True, color='green', label='Height')\nsns.histplot(df_train_shapes['Width'], bins=30, kde=True, color='orange', label='Width')\nplt.title(\"Distribution of Image Dimensions in Train Set\")\nplt.xlabel(\"Pixels\")\nplt.ylabel(\"Count\")\nplt.legend()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-06T19:49:30.502284Z","iopub.execute_input":"2026-01-06T19:49:30.502647Z","iopub.status.idle":"2026-01-06T19:49:30.914378Z","shell.execute_reply.started":"2026-01-06T19:49:30.502608Z","shell.execute_reply":"2026-01-06T19:49:30.913119Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"grayscale_ratio = sum(is_grayscale) / len(is_grayscale) if len(is_grayscale) > 0 else 0\nprint(f\"Grayscale images ratio: {grayscale_ratio:.2%}\")\n\nplt.figure(figsize=(12, 5))\nsns.histplot(mean_intensity, bins=50, kde=True, color='gray')\nplt.title(\"Distribution of Mean Image Intensity\")\nplt.xlabel(\"Mean Intensity (0-255)\")\nplt.ylabel(\"Count\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-06T19:49:30.915762Z","iopub.execute_input":"2026-01-06T19:49:30.916143Z","iopub.status.idle":"2026-01-06T19:49:31.194378Z","shell.execute_reply.started":"2026-01-06T19:49:30.916106Z","shell.execute_reply":"2026-01-06T19:49:31.193459Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_mask(mask_path, img_shape):\n    H, W = img_shape[:2]\n    if H == 0 or W == 0:\n        return np.zeros((0, 0), dtype=np.uint8)\n    if mask_path is None:\n        return np.zeros((H, W), dtype=np.uint8)\n    try:\n        mask = np.load(mask_path)\n    except:\n        return np.zeros((H, W), dtype=np.uint8)\n    if mask.size == 0:\n        return np.zeros((H, W), dtype=np.uint8)\n    if mask.ndim == 1 or mask.size != H * W:\n        mask = np.zeros((H, W), dtype=np.uint8)\n    else:\n        try:\n            mask = mask.reshape((H, W))\n        except:\n            mask = np.zeros((H, W), dtype=np.uint8)\n    mask = (mask > 0).astype(np.uint8)\n    return mask","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-06T19:49:31.195625Z","iopub.execute_input":"2026-01-06T19:49:31.195998Z","iopub.status.idle":"2026-01-06T19:49:31.203821Z","shell.execute_reply.started":"2026-01-06T19:49:31.195961Z","shell.execute_reply":"2026-01-06T19:49:31.202626Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import random\ndef show_samples(pairs, n=20):\n    forged_samples = [p for p in pairs if p[\"has_forgery\"]]\n    authentic_samples = [p for p in pairs if not p[\"has_forgery\"]]\n    \n    # Random sample\n    forged_selected = random.sample(forged_samples, min(n//2, len(forged_samples)))\n    authentic_selected = random.sample(authentic_samples, min(n//2, len(authentic_samples)))\n    \n    samples = forged_selected + authentic_selected\n    random.shuffle(samples)  # shuffle برای تنوع\n    \n    rows = (n + 4) // 5  # 5 در هر ردیف\n    plt.figure(figsize=(20, 4 * rows))\n    for i, p in enumerate(samples):\n        img = cv2.imread(p[\"img_path\"])\n        if img is None:\n            continue\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        mask = load_mask(p[\"mask_path\"], img.shape)\n        img_to_show = img.copy()\n        if p[\"has_forgery\"]:\n            overlay = img.copy()\n            overlay[mask == 1] = [255, 0, 0]  # red overlay\n            img_to_show = cv2.addWeighted(img, 0.7, overlay, 0.3, 0)\n        plt.subplot(rows, 5, i + 1)\n        plt.imshow(img_to_show)\n        plt.axis(\"off\")\n        plt.title(f\"{'Forged' if p['has_forgery'] else 'Authentic'} (ID: {get_id(p['img_path'])})\")\n    plt.tight_layout()\n    plt.show()\n\nshow_samples(pairs, n=20)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-06T19:49:31.204998Z","iopub.execute_input":"2026-01-06T19:49:31.205442Z","iopub.status.idle":"2026-01-06T19:49:33.733972Z","shell.execute_reply.started":"2026-01-06T19:49:31.205414Z","shell.execute_reply":"2026-01-06T19:49:33.732655Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"corr_matrix = df_stats.corr()\nplt.figure(figsize=(8, 6))\nsns.heatmap(corr_matrix, annot=True, cmap='coolwarm')\nplt.title(\"Correlation Matrix for Forged Stats\")\nplt.show()\n\n\nforged_intensity = [mean_intensity[i] for i, p in enumerate(pairs) if p[\"has_forgery\"]]\nauthentic_intensity = [mean_intensity[i] for i, p in enumerate(pairs) if not p[\"has_forgery\"]]\n\nplt.figure(figsize=(12, 5))\nsns.histplot(forged_intensity, bins=30, kde=True, color='red', label='Forged')\nsns.histplot(authentic_intensity, bins=30, kde=True, color='blue', label='Authentic')\nplt.title(\"Mean Intensity Distribution: Forged vs Authentic\")\nplt.xlabel(\"Mean Intensity\")\nplt.ylabel(\"Count\")\nplt.legend()\nplt.show()\n\nprint(\"EDA Complete! This provides a strong foundation for model development.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-06T19:49:33.735281Z","iopub.execute_input":"2026-01-06T19:49:33.735718Z","iopub.status.idle":"2026-01-06T19:49:34.288030Z","shell.execute_reply.started":"2026-01-06T19:49:33.735681Z","shell.execute_reply":"2026-01-06T19:49:34.286966Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## EDA COMPLETE","metadata":{}},{"cell_type":"markdown","source":"## PLEASE UPVOTE\n","metadata":{}},{"cell_type":"code","source":"from PIL import Image, ImageChops, ImageEnhance\nfrom io import BytesIO\nfrom tqdm import tqdm\nimport json\nimport random\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-06T19:56:17.252428Z","iopub.execute_input":"2026-01-06T19:56:17.252755Z","iopub.status.idle":"2026-01-06T19:56:17.272602Z","shell.execute_reply.started":"2026-01-06T19:56:17.252727Z","shell.execute_reply":"2026-01-06T19:56:17.271516Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def visualize_ela_suitability(pil, quality=90):\n    buffer = BytesIO()\n    pil.save(buffer, \"JPEG\", quality=quality)\n    buffer.seek(0)\n\n    recompressed = Image.open(buffer)\n    ela_img = ImageChops.difference(pil, recompressed)\n\n    extrema = ela_img.getextrema()\n    max_diff = max([ex[1] for ex in extrema])\n    if max_diff == 0:\n        max_diff = 1\n\n    scale = 255.0 / max_diff\n    ela_img = ImageEnhance.Brightness(ela_img).enhance(scale)\n\n    plt.figure(figsize=(5, 5))\n    plt.imshow(ela_img)\n    plt.title(\"ELA Visualization\")\n    plt.axis(\"off\")\n    plt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-06T19:58:16.044132Z","iopub.execute_input":"2026-01-06T19:58:16.044449Z","iopub.status.idle":"2026-01-06T19:58:16.052507Z","shell.execute_reply.started":"2026-01-06T19:58:16.044420Z","shell.execute_reply":"2026-01-06T19:58:16.051039Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"forged_train_imgs = [p[\"img_path\"] for p in pairs if p[\"has_forgery\"]]\nprint(\"Forged train images:\", len(forged_train_imgs))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-06T19:58:24.140214Z","iopub.execute_input":"2026-01-06T19:58:24.140681Z","iopub.status.idle":"2026-01-06T19:58:24.147771Z","shell.execute_reply.started":"2026-01-06T19:58:24.140651Z","shell.execute_reply":"2026-01-06T19:58:24.146637Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for img_path in random.sample(forged_train_imgs, 3):\n    print(f\"\\n📸 {os.path.basename(img_path)}\")\n    pil = Image.open(img_path).convert(\"RGB\")\n    visualize_ela_suitability(pil)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-06T19:58:33.214823Z","iopub.execute_input":"2026-01-06T19:58:33.215666Z","iopub.status.idle":"2026-01-06T19:58:33.749887Z","shell.execute_reply.started":"2026-01-06T19:58:33.215633Z","shell.execute_reply":"2026-01-06T19:58:33.748740Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def rle_encode(mask: np.ndarray, fg_val: int = 1) -> str:\n    pixels = mask.T.flatten()\n    dots = np.where(pixels == fg_val)[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])\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-06T19:58:48.486954Z","iopub.execute_input":"2026-01-06T19:58:48.487999Z","iopub.status.idle":"2026-01-06T19:58:48.494805Z","shell.execute_reply.started":"2026-01-06T19:58:48.487956Z","shell.execute_reply":"2026-01-06T19:58:48.493665Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===============================\n# Minimal pipeline_final (SAFE BASELINE)\n# ===============================\n\ndef pipeline_final(pil):\n    \"\"\"\n    Baseline pipeline:\n    - Sab images ko authentic bol dega\n    - Kaggle submission generate ho jayegi\n    \"\"\"\n\n    label = \"authentic\"\n    mask = None\n    dbg = {}\n\n    return label, mask, dbg\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-06T20:01:53.249951Z","iopub.execute_input":"2026-01-06T20:01:53.250996Z","iopub.status.idle":"2026-01-06T20:01:53.256678Z","shell.execute_reply.started":"2026-01-06T20:01:53.250909Z","shell.execute_reply":"2026-01-06T20:01:53.255294Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"TEST_DIR = os.path.join(BASE_DIR, \"test_images\")\n\ntest_image_paths = sorted(glob(os.path.join(TEST_DIR, \"*.png\")))\nprint(\"Test images:\", len(test_image_paths))\n\nrows = []\n\nfor img_path in tqdm(test_image_paths, desc=\"Inference on Test Set\"):\n    pil = Image.open(img_path).convert(\"RGB\")\n    label, mask, dbg = pipeline_final(pil)\n\n    if mask is None:\n        mask = np.zeros(pil.size[::-1], np.uint8)\n    else:\n        mask = np.array(mask, dtype=np.uint8)\n\n    if label == \"authentic\":\n        annot = \"authentic\"\n    else:\n        annot = rle_encode((mask > 0).astype(np.uint8))\n\n    rows.append({\n        \"case_id\": get_id(img_path),\n        \"annotation\": annot\n    })\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-06T20:01:55.734705Z","iopub.execute_input":"2026-01-06T20:01:55.735886Z","iopub.status.idle":"2026-01-06T20:01:55.778704Z","shell.execute_reply.started":"2026-01-06T20:01:55.735839Z","shell.execute_reply":"2026-01-06T20:01:55.777159Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"SAMPLE_SUB = os.path.join(BASE_DIR, \"sample_submission.csv\")\nOUT_PATH = \"submission.csv\"\n\nsub = pd.DataFrame(rows)\nss = pd.read_csv(SAMPLE_SUB)\n\n# dtype fix\nss[\"case_id\"] = ss[\"case_id\"].astype(str)\nsub[\"case_id\"] = sub[\"case_id\"].astype(str)\n\nfinal = ss[[\"case_id\"]].merge(sub, on=\"case_id\", how=\"left\")\nfinal[\"annotation\"] = final[\"annotation\"].fillna(\"authentic\")\n\nfinal[[\"case_id\", \"annotation\"]].to_csv(OUT_PATH, index=False)\n\nprint(f\"\\n✅ Saved submission file: {OUT_PATH}\")\nfinal.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-06T20:03:55.031306Z","iopub.execute_input":"2026-01-06T20:03:55.031876Z","iopub.status.idle":"2026-01-06T20:03:55.073032Z","shell.execute_reply.started":"2026-01-06T20:03:55.031841Z","shell.execute_reply":"2026-01-06T20:03:55.071884Z"}},"outputs":[],"execution_count":null}]}