{"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":"none","dataSources":[{"sourceId":113558,"databundleVersionId":14878066,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":14244215,"sourceType":"datasetVersion","datasetId":9029849}],"dockerImageVersionId":31239,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import json\nimport pandas as pd\nimport cv2\nimport numpy as np\nimport os\nimport torch\nimport torch.nn as nn\nfrom torchvision.models.segmentation import deeplabv3_resnet50\nfrom torchvision import transforms\nfrom tqdm import tqdm\nimport gc\n\n# --- 1. CONFIGURATION ---\nBASE_PATH = '/kaggle/input/recodai-luc-scientific-image-forgery-detection'\nTEST_PATH = os.path.join(BASE_PATH, 'test_images')\nDEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n\n# ⚠️ ENSURE THIS PATH IS CORRECT\nWEIGHTS_PATH = \"/kaggle/input/my-forgery-weights/deeplab_pro_tuned.pth\"\n\n# SETTINGS FROM THE 0.303 FORUM CODE\nTHRESH = 0.40       # Lowered from 0.5 to catch subtle forgeries\nMIN_AREA = 100\n\n# --- 2. MODEL SETUP (NUCLEAR OFFLINE MODE) ---\ndef get_model(weights_path):\n    # Initialize blank model (No downloads)\n    model = deeplabv3_resnet50(\n        weights=None,           \n        weights_backbone=None,  \n        aux_loss=True\n    )\n    \n    # Adjust Heads\n    model.classifier[4] = nn.Conv2d(256, 1, kernel_size=(1, 1), stride=(1, 1))\n    model.aux_classifier[4] = nn.Conv2d(256, 1, kernel_size=(1, 1), stride=(1, 1))\n    \n    # Load Weights\n    if os.path.exists(weights_path):\n        try:\n            state_dict = torch.load(weights_path, map_location=DEVICE)\n            model.load_state_dict(state_dict)\n            print(f\"✅ Loaded weights from {weights_path}\")\n        except Exception as e:\n            print(f\"❌ Error loading weights: {e}\")\n            raise e\n    else:\n        raise FileNotFoundError(f\"Weights not found at {weights_path}\")\n    \n    model.to(DEVICE)\n    model.eval()\n    return model\n\n# --- 3. HELPER FUNCTIONS ---\ndef rle_encode_official(mask):\n    try:\n        pixels = mask.flatten(order='F')\n        dots = np.where(pixels == 1)[0]\n        if len(dots) == 0: return \"authentic\"\n        run_lengths = []\n        prev = -2\n        for b in dots:\n            b = int(b) \n            if b > prev + 1: run_lengths.extend((b + 1, 0))\n            run_lengths[-1] += 1\n            prev = b\n        return json.dumps(run_lengths)\n    except: return \"authentic\"\n\n# --- 4. PREDICTION LOOP WITH MEMORY-SAFE TTA ---\nnormalize = transforms.Compose([\n    transforms.ToPILImage(),\n    transforms.Resize((512, 512)),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225))\n])\n\nif __name__ == \"__main__\":\n    if os.path.exists(TEST_PATH):\n        model = get_model(WEIGHTS_PATH)\n        \n        valid_extensions = ('.png', '.jpg', '.jpeg', '.tif', '.tiff')\n        test_files = sorted([f for f in os.listdir(TEST_PATH) if f.lower().endswith(valid_extensions)])\n        \n        submission_data = []\n        print(f\"Generating predictions for {len(test_files)} images...\")\n        \n        for i, filename in enumerate(tqdm(test_files)):\n            try:\n                # Clear GPU before starting\n                torch.cuda.empty_cache()\n\n                # Load Image\n                filepath = os.path.join(TEST_PATH, filename)\n                img = cv2.imread(filepath)\n                if img is None:\n                    submission_data.append([filename.split('.')[0], \"authentic\"])\n                    continue\n                \n                orig_h, orig_w = img.shape[:2]\n                img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n                \n                # --- SEQUENTIAL TTA (Saves Memory) ---\n                # We accumulate probabilities in CPU RAM, not GPU RAM\n                jason\n                # 1. Normal View\n                img_tensor = normalize(img_rgb).unsqueeze(0).to(DEVICE)\n                with torch.no_grad():\n                    pred = model(img_tensor)['out']\n                    prob_accum = torch.sigmoid(pred).squeeze().cpu().numpy()\n                del img_tensor, pred # Delete immediately from GPU\n                \n                # 2. Horizontal Flip\n                img_flip_h = cv2.flip(img_rgb, 1)\n                img_tensor = normalize(img_flip_h).unsqueeze(0).to(DEVICE)\n                with torch.no_grad():\n                    pred = model(img_tensor)['out']\n                    prob = torch.sigmoid(pred).squeeze().cpu().numpy()\n                    prob_accum += np.fliplr(prob) # Add to accumulator\n                del img_tensor, pred, img_flip_h\n                \n                # 3. Vertical Flip\n                img_flip_v = cv2.flip(img_rgb, 0)\n                img_tensor = normalize(img_flip_v).unsqueeze(0).to(DEVICE)\n                with torch.no_grad():\n                    pred = model(img_tensor)['out']\n                    prob = torch.sigmoid(pred).squeeze().cpu().numpy()\n                    prob_accum += np.flipud(prob) # Add to accumulator\n                del img_tensor, pred, img_flip_v\n                \n                # Average the 3 views\n                prob_map = prob_accum / 3.0\n                \n                # --- POST PROCESSING ---\n                prob_map = cv2.resize(prob_map, (orig_w, orig_h))\n                pred_mask = (prob_map > THRESH).astype(np.uint8)\n                \n                # Smart Filtering (Connected Components)\n                num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(pred_mask, connectivity=8)\n                final_mask = np.zeros_like(pred_mask)\n                for j in range(1, num_labels):\n                    if stats[j, cv2.CC_STAT_AREA] >= MIN_AREA:\n                        final_mask[labels == j] = 1\n                \n                # Encode\n                case_id = filename.rsplit('.', 1)[0]\n                if np.sum(final_mask) == 0:\n                    submission_data.append([case_id, \"authentic\"])\n                else:\n                    rle = rle_encode_official(final_mask)\n                    submission_data.append([case_id, rle if rle not in [\"\", \"[]\", \"authentic\"] else \"authentic\"])\n\n            except Exception as e:\n                case_id = filename.rsplit('.', 1)[0] if 'filename' in locals() else \"unknown\"\n                submission_data.append([case_id, \"authentic\"])\n            \n            # Frequent Garbage Collection\n            if i % 10 == 0:\n                gc.collect()\n\n        df = pd.DataFrame(submission_data, columns=['case_id', 'annotation'])\n        df.to_csv('submission.csv', index=False)\n        print(\"\\n✅ submission.csv created.jason\")\n    else:\n        print(\"Test path not found.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-21T11:53:15.615976Z","iopub.execute_input":"2025-12-21T11:53:15.616322Z","iopub.status.idle":"2025-12-21T11:53:16.602603Z","shell.execute_reply.started":"2025-12-21T11:53:15.616299Z","shell.execute_reply":"2025-12-21T11:53:16.601517Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}