{"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":14174843,"sourceType":"competition"},{"sourceId":13512263,"sourceType":"datasetVersion","datasetId":8579145},{"sourceId":13533632,"sourceType":"datasetVersion","datasetId":8579153}],"dockerImageVersionId":31154,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!cp -r /kaggle/input/segment-anything /kaggle/working/segment-anything\n%cd segment-anything\n!pip install -e .","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-10-30T03:43:56.190083Z","iopub.execute_input":"2025-10-30T03:43:56.190919Z","iopub.status.idle":"2025-10-30T03:44:12.424780Z","shell.execute_reply.started":"2025-10-30T03:43:56.190886Z","shell.execute_reply":"2025-10-30T03:44:12.423932Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%cd /kaggle/working/segment-anything\nimport sys\nfrom segment_anything import sam_model_registry, SamAutomaticMaskGenerator, SamPredictor\n\nsam_checkpoint = \"/kaggle/input/sam-vit-b/sam_vit_b_01ec64.pth\" # the official vit-b checkpoint from https://github.com/facebookresearch/segment-anything?tab=readme-ov-file\n# the segmentation masks are a bit subpar, consider using specialized models such as https://github.com/DevoLearn/CellSAM \nmodel_type = \"vit_b\"\n\ndevice = \"cuda\"\n\nsam = sam_model_registry[model_type](checkpoint=sam_checkpoint)\nsam.to(device=device)\n\nmask_generator = SamAutomaticMaskGenerator(sam)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-30T03:44:12.426662Z","iopub.execute_input":"2025-10-30T03:44:12.427320Z","iopub.status.idle":"2025-10-30T03:44:13.680204Z","shell.execute_reply.started":"2025-10-30T03:44:12.427297Z","shell.execute_reply":"2025-10-30T03:44:13.679595Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\ndef extract_feature(path, spatial_iou_threshold=0.8, min_mask_area=800):\n    import cv2\n    import numpy as np\n    image = cv2.imread(path)\n    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n    #print(image.shape)\n    factor = np.sqrt(image.shape[0] * image.shape[1] / (512 * 512))\n    if factor > 1:\n        image = cv2.resize(image, (int(image.shape[1] / factor), int(image.shape[0] / factor)))\n    #print(image.shape)\n    masks = mask_generator.generate(image)\n    import numpy as np\n    from math import prod\n    from scipy import ndimage\n    import torch\n    \n    def compute_iou(mask1, mask2):\n        \"\"\"Compute Intersection over Union between two masks\"\"\"\n        intersection = np.logical_and(mask1, mask2).sum()\n        union = np.logical_or(mask1, mask2).sum()\n        return intersection / union if union > 0 else 0\n    \n    def compute_bbox_iou(bbox1, bbox2):\n        \"\"\"Compute IoU between two bounding boxes [x, y, w, h]\"\"\"\n        x1, y1, w1, h1 = bbox1\n        x2, y2, w2, h2 = bbox2\n        \n        # Calculate intersection coordinates\n        xi1 = max(x1, x2)\n        yi1 = max(y1, y2)\n        xi2 = min(x1 + w1, x2 + w2)\n        yi2 = min(y1 + h1, y2 + h2)\n        \n        # Calculate intersection area\n        inter_area = max(0, xi2 - xi1) * max(0, yi2 - yi1)\n        \n        # Calculate union area\n        box1_area = w1 * h1\n        box2_area = w2 * h2\n        union_area = box1_area + box2_area - inter_area\n        \n        return inter_area / union_area if union_area > 0 else 0\n    \n    def is_spatially_similar(mask1, mask2, iou_threshold=0.8):\n        \"\"\"Check if two masks are spatially too similar\"\"\"\n        # Method 1: Mask IoU\n        mask_iou = compute_iou(mask1['segmentation'], mask2['segmentation'])\n        if mask_iou > iou_threshold:\n            return True\n        return False\n    \n    # Filter masks by area and spatial similarity\n    filtered_masks = []\n    regions = []\n    \n    for i, item in enumerate(masks):\n        seg = item['segmentation']\n        \n        # Filter by area\n        if item['area'] < min_mask_area:\n            continue\n            \n        # Check spatial similarity with already selected masks\n        is_duplicate = False\n        for existing_item in filtered_masks:\n            if is_spatially_similar(item, existing_item, spatial_iou_threshold):\n                is_duplicate = True\n                break\n                \n        if not is_duplicate:\n            \n            # Extract region\n            rows = np.any(seg, axis=1)\n            cols = np.any(seg, axis=0)\n            \n            if np.any(rows) and np.any(cols):\n                y_min, y_max = np.where(rows)[0][[0, -1]]\n                x_min, x_max = np.where(cols)[0][[0, -1]]\n                \n                # Extract the rectangular region\n                image_transformed = np.logical_and(image, seg[:,:,np.newaxis])\n                bbox_region = image_transformed[y_min:y_max+1, x_min:x_max+1, :]\n                \n                bbox_tensor = torch.tensor(bbox_region).permute(2, 0, 1).to(torch.float32)\n                bbox_tensor = (bbox_tensor - bbox_tensor.min()) / (bbox_tensor.max() - bbox_tensor.min())\n                area_ratio = item['area'] / ((y_max - y_min + 1) * (x_max - x_min + 1))\n                # Additional size filtering\n                if (prod(list(bbox_tensor.shape)) > 300 and \n                    prod(list(bbox_tensor.shape)) < (256 * 256)) and area_ratio < 0.9:\n                    filtered_masks.append(item)\n                    regions.append(bbox_tensor)\n    \n    print(f\"Filtered from {len(masks)} to {len(filtered_masks)} masks\")\n    \n    # If too few regions, return early\n    if len(regions) < 2:\n        return 0.0, [], image, filtered_masks, [], regions\n    \n    # Test with your regions\n    scores = []\n    record = 0.0\n    record_coords = []\n    \n    for i in range(len(regions)):\n        for j in range(len(regions)):\n            if i < j:\n                a = regions[i]\n                b = regions[j]\n                dice_score, aligned1_simple, aligned2_simple = simple_centroid_dice(a, b)\n                scores.append(dice_score)\n                if dice_score > record:\n                    record_coords = [i, j]\n                    record = dice_score\n    \n    return max(scores) if scores else 0.0, scores, image, filtered_masks, record_coords, regions","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-30T03:44:13.681087Z","iopub.execute_input":"2025-10-30T03:44:13.681338Z","iopub.status.idle":"2025-10-30T03:44:13.695710Z","shell.execute_reply.started":"2025-10-30T03:44:13.681322Z","shell.execute_reply":"2025-10-30T03:44:13.695068Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def calculate_centroid(binary_patch):\n    \"\"\"Calculate centroid of binary patch\"\"\"\n    if binary_patch.sum() == 0:\n        return None\n    \n    y_coords, x_coords = np.where(binary_patch > 0)\n    centroid_y = int(np.mean(y_coords))\n    centroid_x = int(np.mean(x_coords))\n    \n    return (centroid_y, centroid_x)\ndef visualize_alignment(patch1, patch2, aligned1, aligned2, centroid1, centroid2, dice):\n    \"\"\"Visualize the alignment process\"\"\"\n    fig, axes = plt.subplots(2, 3, figsize=(15, 10))\n    \n    # Original patches\n    axes[0, 0].imshow(patch1, cmap='gray')\n    axes[0, 0].plot(centroid1[1], centroid1[0], 'r+', markersize=15, markeredgewidth=2)\n    axes[0, 0].set_title(f'Patch 1\\nCentroid: {centroid1}')\n    axes[0, 0].axis('off')\n    \n    axes[0, 1].imshow(patch2, cmap='gray')\n    axes[0, 1].plot(centroid2[1], centroid2[0], 'r+', markersize=15, markeredgewidth=2)\n    axes[0, 1].set_title(f'Patch 2\\nCentroid: {centroid2}')\n    axes[0, 1].axis('off')\n    \n    # Aligned patches\n    axes[1, 0].imshow(aligned1, cmap='gray')\n    axes[1, 0].set_title('Aligned Patch 1')\n    axes[1, 0].axis('off')\n    \n    axes[1, 1].imshow(aligned2, cmap='gray')\n    axes[1, 1].set_title('Aligned Patch 2')\n    axes[1, 1].axis('off')\n    \n    # Overlay\n    overlay = np.stack([aligned1, aligned2, np.zeros_like(aligned1)], axis=2)\n    axes[1, 2].imshow(overlay)\n    axes[1, 2].set_title(f'Overlay (Red: Patch1, Green: Patch2)\\nDice Score: {dice:.3f}')\n    axes[1, 2].axis('off')\n    \n    # Empty subplot for layout\n    axes[0, 2].axis('off')\n    \n    plt.tight_layout()\n    plt.show()\n\n\ndef simple_centroid_dice(region1, region2, visualize=False):\n    \"\"\"Simplified version that's more robust to indexing issues\"\"\"\n    # Convert to numpy arrays if they are tensors\n    if torch.is_tensor(region1):\n        patch1 = region1[0].numpy() if region1.dim() == 3 else region1.numpy()\n    else:\n        patch1 = region1[0] if isinstance(region1, list) else region1\n    \n    if torch.is_tensor(region2):\n        patch2 = region2[0].numpy() if region2.dim() == 3 else region2.numpy()\n    else:\n        patch2 = region2[0] if isinstance(region2, list) else region2\n    \n    # Ensure binary patches\n    patch1_binary = (patch1 > 0).astype(np.uint8)\n    patch2_binary = (patch2 > 0).astype(np.uint8)\n    \n    # Calculate centroids\n    centroid1 = calculate_centroid(patch1_binary)\n    centroid2 = calculate_centroid(patch2_binary)\n    \n    if centroid1 is None or centroid2 is None:\n        return 0.0, None, None\n    \n    # Create large enough canvas\n    h1, w1 = patch1_binary.shape\n    h2, w2 = patch2_binary.shape\n    \n    # Use fixed large canvas size\n    canvas_size = max(h1, w1, h2, w2) * 3\n    canvas1 = np.zeros((canvas_size, canvas_size), dtype=np.uint8)\n    canvas2 = np.zeros((canvas_size, canvas_size), dtype=np.uint8)\n    \n    # Place centroids at center of canvas\n    center = canvas_size // 2\n    \n    # Calculate offsets from centroid to top-left corner\n    offset1_y = centroid1[0]\n    offset1_x = centroid1[1]\n    offset2_y = centroid2[0]\n    offset2_x = centroid2[1]\n    \n    # Calculate placement coordinates\n    y1_start = center - offset1_y\n    x1_start = center - offset1_x\n    y2_start = center - offset2_y\n    x2_start = center - offset2_x\n    \n    # Use try-except for robust placement\n    try:\n        canvas1[y1_start:y1_start+h1, x1_start:x1_start+w1] = patch1_binary\n        canvas2[y2_start:y2_start+h2, x2_start:x2_start+w2] = patch2_binary\n        \n        # Compute Dice score\n        intersection = np.logical_and(canvas1, canvas2).sum()\n        union = canvas1.sum() + canvas2.sum()\n        dice = 2 * intersection / union if union > 0 else 0.0\n        if visualize:\n            visualize_alignment(patch1_binary, patch2_binary, canvas1, canvas2, \n                              centroid1, centroid2, dice)\n        return dice, canvas1, canvas2\n        \n    except Exception as e:\n        print(f\"Alignment error: {e}\")\n        return 0.0, None, None","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-30T03:44:13.697400Z","iopub.execute_input":"2025-10-30T03:44:13.697608Z","iopub.status.idle":"2025-10-30T03:44:13.713564Z","shell.execute_reply.started":"2025-10-30T03:44:13.697593Z","shell.execute_reply":"2025-10-30T03:44:13.712895Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nsubm_df = pd.read_csv(\"/kaggle/input/recodai-luc-scientific-image-forgery-detection/sample_submission.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-30T03:44:13.714182Z","iopub.execute_input":"2025-10-30T03:44:13.714445Z","iopub.status.idle":"2025-10-30T03:44:13.730005Z","shell.execute_reply.started":"2025-10-30T03:44:13.714420Z","shell.execute_reply":"2025-10-30T03:44:13.729389Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#for item in subm_df['case_id']:\n#    print(item)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-30T03:44:13.730828Z","iopub.execute_input":"2025-10-30T03:44:13.731128Z","iopub.status.idle":"2025-10-30T03:44:13.737301Z","shell.execute_reply.started":"2025-10-30T03:44:13.731110Z","shell.execute_reply":"2025-10-30T03:44:13.736670Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport numpy as np\nimport os\nimport numpy.typing as npt\nimport json\nfrom skimage.transform import resize\nfrom PIL import Image\nimport cv2\nfrom numba import types\nimport numba\n#roots = os.listdir(\"/app/notebooks/recodai-luc-scientific-image-forgery-detection/train_images/forged/\") \n@numba.jit(nopython=True)\ndef _rle_encode_jit(x: npt.NDArray, fg_val: int = 1) -> list[int]:\n    \"\"\"Numba-jitted RLE encoder.\"\"\"\n    dots = np.where(x.T.flatten() == fg_val)[0]\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    return run_lengths\n\n\ndef rle_encode(masks: list[npt.NDArray], fg_val: int = 1) -> str:\n    \"\"\"\n    Adapted from contrails RLE https://www.kaggle.com/code/inversion/contrails-rle-submission\n    Args:\n        masks: list of numpy array of shape (height, width), 1 - mask, 0 - background\n    Returns: run length encodings as a string, with each RLE JSON-encoded and separated by a semicolon.\n    \"\"\"\n    return ';'.join([json.dumps(_rle_encode_jit(x, fg_val)) for x in masks])\nfrom sklearn.naive_bayes import GaussianNB\nimport joblib\nclf = joblib.load(\"/kaggle/input/sam-vit-b/gaussian_nb_forgery1\")\nscore, counter = 0, 0\npreds = []\nfiles = os.listdir(\"/kaggle/input/recodai-luc-scientific-image-forgery-detection/test_images\")\n#files = os.listdir(\"/kaggle/input/recodai-luc-scientific-image-forgery-detection/train_images/authentic\")\nfiles = [x.split(\".\")[0] for x in files]\ngt = []\nshapes = []\ncounter = 0\npath = \"\"\nfor fn in files:\n    '''\n    shape = \"\"\n    if counter % 2 == 1:\n        path = f\"/kaggle/input/recodai-luc-scientific-image-forgery-detection/train_images/forged/{fn}.png\"\n        mask = f\"/kaggle/input/recodai-luc-scientific-image-forgery-detection/train_masks/{fn}.npy\"\n        print(np.load(mask).shape)\n        gt.append(rle_encode_from_mask(np.load(mask)))\n    else:\n        path = f\"/kaggle/input/recodai-luc-scientific-image-forgery-detection/train_images/authentic/{fn}.png\"\n        gt.append(\"authentic\")\n    '''\n    path = f\"/kaggle/input/recodai-luc-scientific-image-forgery-detection/test_images/{fn}.png\"\n    #path = f\"/kaggle/input/recodai-luc-scientific-image-forgery-detection/train_images/forged/20985.png\"\n    pred = None\n    try:\n        #raise ValueError()\n        orig_shape = cv2.imread(path)[:,:,0].shape\n        a, b, image, masks, sim, regions = extract_feature(path)\n        x_pred = np.array([[max(b), sorted(b)[-2]]])\n        pred = clf.predict(x_pred)\n        #if pred == 1:\n        if max(b) > 0.98:\n            pred = np.logical_or(masks[sim[0]]['segmentation'], masks[sim[1]]['segmentation'])\n            print(masks[0]['segmentation'].shape)\n            pred = resize(pred.astype(float), orig_shape, order=0, anti_aliasing=False, preserve_range=True)\n            pred = np.ones_like(pred)\n            #pred = np.array(pred.T)\n            #shape = pred.shape\n            pred = rle_encode([pred])\n        else:\n            pred = \"authentic\"\n    except Exception as e:\n        print(e, fn)\n        pred = \"authentic\"\n    preds.append(pred)\n    #shapes.append(shape)\n    counter += 1\n# tip: are there actually many cases where it gets reduced to 2 masks? there seems to be a couple of success cases\n# with just 2 masks","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-30T03:44:13.738032Z","iopub.execute_input":"2025-10-30T03:44:13.738188Z","iopub.status.idle":"2025-10-30T03:44:16.541978Z","shell.execute_reply.started":"2025-10-30T03:44:13.738176Z","shell.execute_reply":"2025-10-30T03:44:16.541167Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"idx = min(len(files), len(preds))\n\n# Convert shape tuples to JSON array strings\n#shape_strings = [json.dumps(list(shape)) for shape in shapes[:idx]]\n\n#solution = pd.DataFrame({'case_id': files[:idx], 'annotation': gt[:idx]})\nsubmission = pd.DataFrame({'case_id': files[:idx], 'annotation': preds[:idx]})","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-30T03:44:16.542895Z","iopub.execute_input":"2025-10-30T03:44:16.543169Z","iopub.status.idle":"2025-10-30T03:44:16.547344Z","shell.execute_reply.started":"2025-10-30T03:44:16.543148Z","shell.execute_reply":"2025-10-30T03:44:16.546678Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-30T03:44:16.547997Z","iopub.execute_input":"2025-10-30T03:44:16.548228Z","iopub.status.idle":"2025-10-30T03:44:16.577183Z","shell.execute_reply.started":"2025-10-30T03:44:16.548207Z","shell.execute_reply":"2025-10-30T03:44:16.576458Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission.to_csv(\"../submission.csv\", index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-30T03:44:16.579229Z","iopub.execute_input":"2025-10-30T03:44:16.579511Z","iopub.status.idle":"2025-10-30T03:44:16.591194Z","shell.execute_reply.started":"2025-10-30T03:44:16.579491Z","shell.execute_reply":"2025-10-30T03:44:16.590609Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pd.read_csv(\"../submission.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-30T03:44:16.591809Z","iopub.execute_input":"2025-10-30T03:44:16.592033Z","iopub.status.idle":"2025-10-30T03:44:16.607541Z","shell.execute_reply.started":"2025-10-30T03:44:16.592014Z","shell.execute_reply":"2025-10-30T03:44:16.606775Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}