{"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":14456136,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":281674861,"sourceType":"kernelVersion"},{"sourceId":90860,"sourceType":"modelInstanceVersion","isSourceIdPinned":false,"modelInstanceId":76172,"modelId":100857},{"sourceId":4534,"sourceType":"modelInstanceVersion","isSourceIdPinned":false,"modelInstanceId":3326,"modelId":986},{"sourceId":648498,"sourceType":"modelInstanceVersion","isSourceIdPinned":false,"modelInstanceId":489174,"modelId":504592}],"dockerImageVersionId":31153,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Visualization","metadata":{}},{"cell_type":"code","source":"# PART 1","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport glob\n\nPATH_DATASET = \"/kaggle/input/recodai-luc-scientific-image-forgery-detection\"\nauthentic_images = glob.glob(os.path.join(PATH_DATASET, 'train_images', 'authentic', '*.png'))\nforged_images = glob.glob(os.path.join(PATH_DATASET, 'train_images', 'forged', '*.png'))\n\nprint(f\"Found {len(authentic_images)} authentic images.\")\nprint(f\"Found {len(forged_images)} forged images.\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-11-26T05:29:51.964246Z","iopub.execute_input":"2025-11-26T05:29:51.964646Z","iopub.status.idle":"2025-11-26T05:29:52.0303Z","shell.execute_reply.started":"2025-11-26T05:29:51.964628Z","shell.execute_reply":"2025-11-26T05:29:52.029539Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Authentic cases","metadata":{}},{"cell_type":"code","source":"# PART 2 ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-26T05:30:19.480339Z","iopub.execute_input":"2025-11-26T05:30:19.480927Z","iopub.status.idle":"2025-11-26T05:30:19.484286Z","shell.execute_reply.started":"2025-11-26T05:30:19.480894Z","shell.execute_reply":"2025-11-26T05:30:19.483569Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import random\nimport matplotlib.pyplot as plt\nimport matplotlib.image as mpimg\n\n# Select a random subset of authentic images\nnum_images_to_show = 12  # 3x4 grid\nrandom_authentic_images = random.sample(authentic_images, min(num_images_to_show, len(authentic_images)))\n\n# Display the images in a grid\nfig, axes = plt.subplots(3, 4, figsize=(10, 8))\naxes = axes.flatten()\n\nfor i, img_path in enumerate(random_authentic_images):\n    img = mpimg.imread(img_path)\n    axes[i].imshow(img)\n    axes[i].axis('off') # Hide axes\n    axes[i].set_title(os.path.basename(img_path), fontsize=8) # Add filename as title\n\n# Hide any unused subplots\nfor j in range(i + 1, len(axes)):\n    axes[j].axis('off')\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-26T05:30:19.77044Z","iopub.execute_input":"2025-11-26T05:30:19.770753Z","iopub.status.idle":"2025-11-26T05:30:23.643203Z","shell.execute_reply.started":"2025-11-26T05:30:19.770732Z","shell.execute_reply":"2025-11-26T05:30:23.642472Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Forged cases with annotations","metadata":{}},{"cell_type":"code","source":"# PART 3","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-26T05:30:36.448257Z","iopub.execute_input":"2025-11-26T05:30:36.448839Z","iopub.status.idle":"2025-11-26T05:30:36.451984Z","shell.execute_reply.started":"2025-11-26T05:30:36.448811Z","shell.execute_reply":"2025-11-26T05:30:36.451235Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import glob\n\n# Assuming the masks are in a 'train_masks' directory within the data_dir\nmask_dir = os.path.join(PATH_DATASET, 'train_masks')\n\n# Find all .npy files in the train_masks directory and store in a dictionary\nmask_files_dict = {}\nfor mask_path in glob.glob(os.path.join(mask_dir, '*.npy')):\n    basename = os.path.basename(mask_path)\n    filename_without_extension, _ = os.path.splitext(basename) # Remove extension\n    mask_files_dict[filename_without_extension] = mask_path\n\nprint(f\"Found {len(mask_files_dict)} mask files and stored in a dictionary with keys as filenames without extensions.\")\nmask_dict = [f\"{k}: {v}\" for k, v in list(mask_files_dict.items())]\nprint(\"\\n\".join(mask_dict[:5]))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-26T05:30:36.721089Z","iopub.execute_input":"2025-11-26T05:30:36.721344Z","iopub.status.idle":"2025-11-26T05:30:36.766302Z","shell.execute_reply.started":"2025-11-26T05:30:36.721325Z","shell.execute_reply":"2025-11-26T05:30:36.76578Z"},"_kg_hide-input":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# PART 3 ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-26T05:31:11.905991Z","iopub.execute_input":"2025-11-26T05:31:11.906527Z","iopub.status.idle":"2025-11-26T05:31:11.909442Z","shell.execute_reply.started":"2025-11-26T05:31:11.906502Z","shell.execute_reply":"2025-11-26T05:31:11.908922Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tqdm.auto import tqdm\nimport numpy as np\n\n# Load all masks and store their shapes\nall_mask_shapes = []\nfor filename_without_extension, mask_path in tqdm(mask_files_dict.items()):\n    mask = np.load(mask_path)\n    all_mask_shapes.append(len(mask.shape))\n\nprint(f\"Loaded shapes for {len(all_mask_shapes)} masks.\")\nprint(\"Mask shapes:\", set(all_mask_shapes))","metadata":{"trusted":true,"_kg_hide-input":true,"execution":{"iopub.status.busy":"2025-11-26T05:31:12.239843Z","iopub.execute_input":"2025-11-26T05:31:12.240351Z","iopub.status.idle":"2025-11-26T05:31:36.126609Z","shell.execute_reply.started":"2025-11-26T05:31:12.24033Z","shell.execute_reply":"2025-11-26T05:31:36.125814Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_mask(mask_path: str):\n    mask_raw = np.load(mask_path)\n    # Sum across the first dimension and binarize: 1 if any channel has a value > 0, 0 otherwise.\n    mask = np.zeros_like(mask_raw[0, :, :], dtype=np.uint8)\n    for c in range(mask_raw.shape[0]):\n        mask[mask_raw[c, :, :] > 0] = c + 1\n    return mask","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-26T05:32:03.016307Z","iopub.execute_input":"2025-11-26T05:32:03.017009Z","iopub.status.idle":"2025-11-26T05:32:03.020898Z","shell.execute_reply.started":"2025-11-26T05:32:03.016985Z","shell.execute_reply":"2025-11-26T05:32:03.020174Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Step 1: Match images and masks\n# We'll match based on the base filename (without extension)\nimage_mask_pairs = []\n\nfor image_path in forged_images:\n    image_basename = os.path.basename(image_path)\n    filename_without_extension, _ = os.path.splitext(image_basename)\n    mask_path = mask_files_dict[filename_without_extension]\n    image_mask_pairs.append((image_path, mask_path))\n\nprint(f\"Found {len(image_mask_pairs)} image-mask pairs.\")","metadata":{"trusted":true,"_kg_hide-input":true,"execution":{"iopub.status.busy":"2025-11-26T05:32:05.906988Z","iopub.execute_input":"2025-11-26T05:32:05.907641Z","iopub.status.idle":"2025-11-26T05:32:05.916701Z","shell.execute_reply.started":"2025-11-26T05:32:05.90761Z","shell.execute_reply":"2025-11-26T05:32:05.916006Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Define a list of colors for the different mask levels (excluding background 0)\n# You can customize this list with more colors if you expect more levels\nmask_colors = ['red', 'blue', 'green', 'purple', 'orange', 'brown', 'pink', 'gray', 'olive', 'cyan']","metadata":{"trusted":true,"_kg_hide-input":true,"execution":{"iopub.status.busy":"2025-11-26T05:32:09.297864Z","iopub.execute_input":"2025-11-26T05:32:09.298144Z","iopub.status.idle":"2025-11-26T05:32:09.301973Z","shell.execute_reply.started":"2025-11-26T05:32:09.298124Z","shell.execute_reply":"2025-11-26T05:32:09.301167Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Step 2: Select random subset\nnum_pairs_to_show = 12 # For a 12-row grid\nrandom_pairs = random.sample(image_mask_pairs, min(num_pairs_to_show, len(image_mask_pairs)))\n\nfor i, (image_path, mask_path) in enumerate(random_pairs):\n    # Step 3: Visualize in grid (3 columns, num_pairs_to_show rows)\n    fig, axes = plt.subplots(1, 3, figsize=(12, 4)) # Adjust figsize as needed\n    # Display image in the first column\n    img = mpimg.imread(image_path)\n    axes[0].imshow(img)\n    axes[0].axis('off')\n    axes[0].set_title(os.path.basename(image_path), fontsize=8)\n\n    # Load the mask as multilabel\n    mask = load_mask(mask_path)\n    levels = np.unique(mask)[:-1] + 0.5\n\n    # Display image with mask contour in the second column\n    axes[1].imshow(img) # Display the original image\n\n    # Find and draw contours on the second column axes\n    axes[1].contour(mask, levels=levels, colors=mask_colors, linewidths=1)\n    axes[1].axis('off')\n    axes[1].set_title(\"Mask Contour\", fontsize=8)\n\n    # Display mask in the third column\n    # Assuming the mask is a grayscale or binary image, adjust colormap if necessary\n    axes[2].imshow(mask, cmap='viridis', interpolation='nearest')\n    axes[2].axis('off')\n    axes[2].set_title(os.path.basename(mask_path), fontsize=8)\n    fig.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-26T05:32:13.38799Z","iopub.execute_input":"2025-11-26T05:32:13.388276Z","iopub.status.idle":"2025-11-26T05:32:17.80214Z","shell.execute_reply.started":"2025-11-26T05:32:13.388256Z","shell.execute_reply":"2025-11-26T05:32:17.801338Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Get just the filenames without the path\nauthentic_filenames = [os.path.basename(img_path) for img_path in authentic_images]\nforged_filenames = [os.path.basename(img_path) for img_path in forged_images]\n\n# Find the intersection of the two sets of filenames\noverlapping_filenames = list(set(authentic_filenames).intersection(forged_filenames))\n\nprint(f\"Found {len(overlapping_filenames)} overlapping filenames in authentic and forged folders.\")","metadata":{"trusted":true,"_kg_hide-input":true,"execution":{"iopub.status.busy":"2025-11-26T05:32:22.699023Z","iopub.execute_input":"2025-11-26T05:32:22.699579Z","iopub.status.idle":"2025-11-26T05:32:22.706657Z","shell.execute_reply.started":"2025-11-26T05:32:22.699557Z","shell.execute_reply":"2025-11-26T05:32:22.70604Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create dictionaries mapping filename without extension to full path for quicker lookup\nauthentic_image_dict = {os.path.splitext(os.path.basename(img_path))[0]: img_path for img_path in authentic_images}\nforged_image_dict = {os.path.splitext(os.path.basename(img_path))[0]: img_path for img_path in forged_images}\n\n# Find filenames that exist in both authentic and forged sets (using keys without extensions)\noverlapping_filenames_without_extension = list(set(authentic_image_dict.keys()).intersection(forged_image_dict.keys()))\n\n# Create pairs of (authentic_path, forged_path, mask_path) for overlapping filenames\nmatching_pairs_with_mask = []\nfor filename_without_extension in overlapping_filenames_without_extension:\n    authentic_path = authentic_image_dict[filename_without_extension]\n    forged_path = forged_image_dict[filename_without_extension]\n    # Check if a mask exists for this forged image (mask_files_dict already uses keys without extension)\n    if filename_without_extension in mask_files_dict:\n        mask_path = mask_files_dict[filename_without_extension]\n        matching_pairs_with_mask.append((authentic_path, forged_path, mask_path))\n    else:\n        print(f\"Warning: No mask found for forged image with filename (without extension): {filename_without_extension}\")\n\nprint(f\"Found {len(matching_pairs_with_mask)} matching image-mask pairs with the same filename (without extension).\")","metadata":{"trusted":true,"_kg_hide-input":true,"execution":{"iopub.status.busy":"2025-11-26T05:32:24.141636Z","iopub.execute_input":"2025-11-26T05:32:24.142337Z","iopub.status.idle":"2025-11-26T05:32:24.157401Z","shell.execute_reply.started":"2025-11-26T05:32:24.142311Z","shell.execute_reply":"2025-11-26T05:32:24.15656Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def display_image_with_annotations(image, annotations, ax):\n    \"\"\"Overlays masks on an image and returns the Matplotlib Figure object.\"\"\"\n    # 1. Create the Figure and Axes objects explicitly\n    # 2. Display the base image on the axes\n    ax.imshow(image)\n    ax.axis('off') # Hide the axis ruler/numbers\n    # 3. If no annotations, return the figure with just the base image\n    if not annotations:\n        return fig\n    # 4. Sort masks: Largest first\n    annotations.sort(key=lambda x: x['area'], reverse=True)\n    # 5. Create the RGBA overlay layer\n    h, w = image.shape[:2]\n    overlay_rgba = np.zeros((h, w, 4), dtype=np.float32)\n    # 6. Draw masks onto the overlay layer\n    for ann in annotations:\n        mask = ann['segmentation']\n        rgb = np.random.random(3) # Random RGB color\n        overlay_rgba[mask, :3] = rgb # Color\n        overlay_rgba[mask, 3] = 0.5  # Alpha (Transparency)\n    # 7. Add the overlay to the axes\n    ax.imshow(overlay_rgba)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-26T05:33:20.902642Z","iopub.execute_input":"2025-11-26T05:33:20.902937Z","iopub.status.idle":"2025-11-26T05:33:20.908966Z","shell.execute_reply.started":"2025-11-26T05:33:20.902914Z","shell.execute_reply":"2025-11-26T05:33:20.908338Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Analyses","metadata":{}},{"cell_type":"code","source":"all_mask_instances = []\nfor filename_without_extension, mask_path in tqdm(mask_files_dict.items()):\n    mask = np.load(mask_path)\n    all_mask_instances.append(mask.shape[0])\n\nprint(f\"Loaded shapes for {len(all_mask_instances)} masks.\")\nprint(\"Mask shapes:\", set(all_mask_instances))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-26T05:33:35.243298Z","iopub.execute_input":"2025-11-26T05:33:35.243585Z","iopub.status.idle":"2025-11-26T05:33:37.959823Z","shell.execute_reply.started":"2025-11-26T05:33:35.243563Z","shell.execute_reply":"2025-11-26T05:33:37.959083Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Discover how many instances are per image","metadata":{}},{"cell_type":"code","source":"import collections\nimport seaborn as sns\n\n# Count the occurrences of each instance count\ninstance_counts = collections.Counter(all_mask_instances)\nsorted_instance_counts = dict(sorted(instance_counts.items()))\n\n# Create a bar plot of the instance counts\nplt.figure(figsize=(8, 3))\nsns.barplot(x=list(sorted_instance_counts.keys()), y=list(sorted_instance_counts.values()))\nplt.title(\"Mask Instance Counts\")\nplt.xlabel(\"Number of Instances in Mask\")\nplt.ylabel(\"Occurances\")\nplt.grid(axis='y', alpha=0.75)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-26T05:33:40.703414Z","iopub.execute_input":"2025-11-26T05:33:40.703685Z","iopub.status.idle":"2025-11-26T05:33:40.843603Z","shell.execute_reply.started":"2025-11-26T05:33:40.703666Z","shell.execute_reply":"2025-11-26T05:33:40.842854Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Explore the ratios of object to image size","metadata":{}},{"cell_type":"code","source":"# Initialize a list to store all area ratios\nall_area_ratios = []\n\n# Iterate through mask files\nfor filename_without_extension, mask_path in tqdm(mask_files_dict.items()):\n    # Load the raw mask data (not using the load_mask function as we need individual layers)\n    mask_raw = np.load(mask_path)\n    # Get image dimensions from the mask shape (assuming mask and image have same dimensions)\n    num_instances, height, width = mask_raw.shape\n    total_image_area = height * width\n    # List to store ratios for the current mask\n    mask_area_ratios = []\n\n    # Iterate through mask instances (layers)\n    for instance_layer in mask_raw:\n        # Calculate segmented area for the instance\n        segmented_area = np.sum(instance_layer > 0)\n        # Calculate area ratio\n        area_ratio = segmented_area / total_image_area\n        # Store the ratio\n        mask_area_ratios.append(area_ratio)\n\n    # Extend the main list with ratios from the current mask\n    all_area_ratios.extend(mask_area_ratios)\n\nprint(f\"Calculated area ratios for {len(all_area_ratios)} instances across all masks.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-26T05:33:51.492968Z","iopub.execute_input":"2025-11-26T05:33:51.493677Z","iopub.status.idle":"2025-11-26T05:33:58.23497Z","shell.execute_reply.started":"2025-11-26T05:33:51.493649Z","shell.execute_reply":"2025-11-26T05:33:58.234192Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Analyze and visualize the distribution of area ratios\nplt.figure(figsize=(10, 4))\nsns.histplot(all_area_ratios, bins=50, kde=True) # Using 50 bins to show the distribution shape\nplt.title(\"Distribution of Segmented Area Ratios\")\nplt.xlabel(\"Area Ratio (Segmented Area / Total Image Area)\")\nplt.ylabel(\"Frequency\")\nplt.grid(axis='y', alpha=0.75)\nplt.show()\n\n# Print some basic statistics about the area ratios\nprint(\"\\nBasic statistics for area ratios:\")\nprint(f\"Mean: {np.mean(all_area_ratios):.4f}\")\nprint(f\"Median: {np.median(all_area_ratios):.4f}\")\nprint(f\"Standard Deviation: {np.std(all_area_ratios):.4f}\")\nprint(f\"Min: {np.min(all_area_ratios):.4f}\")\nprint(f\"Max: {np.max(all_area_ratios):.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-26T05:32:44.010611Z","iopub.execute_input":"2025-11-26T05:32:44.010844Z","iopub.status.idle":"2025-11-26T05:32:44.334598Z","shell.execute_reply.started":"2025-11-26T05:32:44.010827Z","shell.execute_reply":"2025-11-26T05:32:44.333934Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","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\n\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,"execution":{"iopub.status.busy":"2025-11-26T05:39:32.797468Z","iopub.execute_input":"2025-11-26T05:39:32.798336Z","iopub.status.idle":"2025-11-26T05:39:32.803109Z","shell.execute_reply.started":"2025-11-26T05:39:32.798307Z","shell.execute_reply":"2025-11-26T05:39:32.802411Z"}},"outputs":[],"execution_count":null},{"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,"execution":{"iopub.status.busy":"2025-11-26T05:39:35.329198Z","iopub.execute_input":"2025-11-26T05:39:35.32968Z","iopub.status.idle":"2025-11-26T05:39:35.337658Z","shell.execute_reply.started":"2025-11-26T05:39:35.329657Z","shell.execute_reply":"2025-11-26T05:39:35.337106Z"}},"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,"execution":{"iopub.status.busy":"2025-11-26T05:39:37.930117Z","iopub.execute_input":"2025-11-26T05:39:37.930634Z","iopub.status.idle":"2025-11-26T05:39:40.877521Z","shell.execute_reply.started":"2025-11-26T05:39:37.930612Z","shell.execute_reply":"2025-11-26T05:39:40.876933Z"}},"outputs":[],"execution_count":null},{"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,"execution":{"iopub.status.busy":"2025-11-26T05:39:44.360722Z","iopub.execute_input":"2025-11-26T05:39:44.361067Z","iopub.status.idle":"2025-11-26T05:39:44.365681Z","shell.execute_reply.started":"2025-11-26T05:39:44.361046Z","shell.execute_reply":"2025-11-26T05:39:44.365008Z"}},"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\", ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-26T05:39:44.892411Z","iopub.execute_input":"2025-11-26T05:39:44.892679Z","iopub.status.idle":"2025-11-26T05:39:44.902868Z","shell.execute_reply.started":"2025-11-26T05:39:44.892659Z","shell.execute_reply":"2025-11-26T05:39:44.902296Z"}},"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":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-26T05:40:03.476006Z","iopub.execute_input":"2025-11-26T05:40:03.47628Z","iopub.status.idle":"2025-11-26T05:40:04.620817Z","shell.execute_reply.started":"2025-11-26T05:40:03.476259Z","shell.execute_reply":"2025-11-26T05:40:04.619917Z"}},"outputs":[],"execution_count":null},{"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,"execution":{"iopub.status.busy":"2025-11-26T05:40:16.142627Z","iopub.execute_input":"2025-11-26T05:40:16.143363Z","iopub.status.idle":"2025-11-26T05:40:16.180907Z","shell.execute_reply.started":"2025-11-26T05:40:16.143337Z","shell.execute_reply":"2025-11-26T05:40:16.180378Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}