{"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":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":115267,"databundleVersionId":13761094,"isSourceIdPinned":false},{"sourceType":"datasetVersion","sourceId":16370898,"datasetId":10494793,"databundleVersionId":17363553}],"dockerImageVersionId":31329,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# ==========================================\n# STRONG PIPELINE: ALIKED + LightGlue + Smart Pairing\n# ==========================================\n\nimport subprocess\n\nsubprocess.run(['pip', 'install', '/kaggle/input/lightglue-offline/lightglue_pkg/*'], \n               capture_output=True)\n\nimport torch\nfrom lightglue import LightGlue, ALIKED\nfrom lightglue.utils import load_image, rbd\nimport cv2\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport glob\nimport random\nfrom collections import defaultdict\n\n# ==========================================\n# GROUP IMAGES BY SCENE\n# ==========================================\nbase_path = '/kaggle/input/competitions/image-matching-challenge-2025-ongoing'\nall_images = glob.glob(base_path + '/train/**/*.png', recursive=True)\n\nscenes = defaultdict(list)\nfor p in all_images:\n    scene_name = p.split('/')[-2]\n    scenes[scene_name].append(p)\n\n# Print summary\nprint(\"Available Scenes:\")\nfor scene, imgs in sorted(scenes.items(), key=lambda x: len(x[1]), reverse=True):\n    print(f\"  • {scene}: {len(imgs)} images\")\n\n# ==========================================\n# SELECT A GOOD SCENE AND CONSECUTIVE PAIR\n# ==========================================\nchosen_scene = \"pt_brandenburg_british_buckingham\"          # Change to any scene you want\n# chosen_scene = \"pt_brandenburg_british_buckingham\"\n# chosen_scene = \"fbk_vineyard\"\n\nscene_images = scenes[chosen_scene]\nprint(f\"\\nSelected Scene: {chosen_scene} ({len(scene_images)} images)\")\n\n# Take two consecutive images (usually have good overlap)\nidx1 = 20     # Try different numbers: 0, 5, 10, 20, 30...\nidx2 = idx1 + 1\n\nimg1_path = scene_images[idx1]\nimg2_path = scene_images[idx2]\n\nprint(f\"Pair: {img1_path.split('/')[-1]}  <-->  {img2_path.split('/')[-1]}\")\n\n# ==========================================\n# MODEL SETUP (ALIKED is much better here)\n# ==========================================\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n\nextractor = ALIKED(\n    max_num_keypoints=2048, \n    detection_threshold=0.01,\n    nms_radius=2\n).eval().to(device)\n\nmatcher = LightGlue(\n    features='aliked',\n    depth_confidence=0.95,\n    width_confidence=0.95\n).eval().to(device)\n\n# ==========================================\n# MATCHING\n# ==========================================\nimage0 = load_image(img1_path).to(device)\nimage1 = load_image(img2_path).to(device)\n\nfeats0 = extractor.extract(image0.unsqueeze(0))\nfeats1 = extractor.extract(image1.unsqueeze(0))\n\nmatches01 = matcher({\"image0\": feats0, \"image1\": feats1})\nfeats0, feats1, matches01 = [rbd(x) for x in [feats0, feats1, matches01]]\n\nmatches = matches01[\"matches\"]\nconf = matches01[\"scores\"].detach().cpu().numpy()\n\nprint(f\"\\nRaw Matches: {len(matches)}\")\n\n# RANSAC\nkpts0 = feats0['keypoints'][matches[:, 0]].cpu().numpy()\nkpts1 = feats1['keypoints'][matches[:, 1]].cpu().numpy()\n\ngood = np.zeros(len(matches), dtype=bool)\nif len(kpts0) >= 8:\n    _, mask = cv2.findHomography(\n        kpts0.reshape(-1,1,2).astype(np.float32),\n        kpts1.reshape(-1,1,2).astype(np.float32),\n        cv2.RANSAC, 3.0\n    )\n    if mask is not None:\n        inliers = mask.ravel().astype(bool)\n        good = inliers & (conf > 0.2)\n        print(f\"Good Matches after RANSAC: {good.sum()}\")\nelse:\n    print(\"Too few matches for RANSAC\")\n\n# ==========================================\n# VISUALIZATION\n# ==========================================\nimg0 = cv2.cvtColor(cv2.imread(img1_path), cv2.COLOR_BGR2RGB)\nimg1_cv = cv2.cvtColor(cv2.imread(img2_path), cv2.COLOR_BGR2RGB)\n\nh = max(img0.shape[0], img1_cv.shape[0])\nw = img0.shape[1] + img1_cv.shape[1]\ncanvas = np.zeros((h, w, 3), dtype=np.uint8)\ncanvas[:img0.shape[0], :img0.shape[1]] = img0\ncanvas[:img1_cv.shape[0], img0.shape[1]:] = img1_cv\n\nfor i, (p0, p1) in enumerate(zip(kpts0, kpts1)):\n    if good[i]:\n        color = (random.randint(100,255), random.randint(100,255), random.randint(100,255))\n        cv2.line(canvas, tuple(map(int, p0)), \n                (int(p1[0]) + img0.shape[1], int(p1[1])), color, 2)\n\nplt.figure(figsize=(22, 12))\nplt.imshow(canvas)\nplt.title(f\"Scene: {chosen_scene}\\nGood Matches: {good.sum()} / Raw: {len(matches)}\")\nplt.axis('off')\nplt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# OPTIMIZED SUBMISSION - MAX PAIRS\n# ==========================================\n\nimport subprocess\n\nsubprocess.run(['pip', 'install', '/kaggle/input/lightglue-offline/lightglue_pkg/*'], \n               capture_output=True)\n\nimport torch\nfrom lightglue import LightGlue, ALIKED\nfrom lightglue.utils import load_image, rbd\nimport cv2, numpy as np, glob, pandas as pd\nfrom pathlib import Path\nfrom collections import defaultdict\n\nprint(\"🚀 Optimized Submission Starting...\")\n\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f\"✅ Using: {device}\")\nif device.type == 'cuda':\n    print(f\"   GPU: {torch.cuda.get_device_name(0)}\")\n\nbase_path = '/kaggle/input/competitions/image-matching-challenge-2025-ongoing'\nall_images = glob.glob(base_path + '/train/**/*.png', recursive=True)\n\nscenes = defaultdict(list)\nfor p in all_images:\n    scenes[p.split('/')[-2]].append(p)\n\nprint(f\"Scenes: {len(scenes)} | Images: {len(all_images)}\")\n\n# ---- OPTIMIZED SETTINGS ----\nextractor = ALIKED(\n    max_num_keypoints=2048,       # ↑ was 1536 — zyada keypoints = zyada matches\n    detection_threshold=0.01,    # ↓ was 0.02 — sensitive detection\n    nms_radius=2\n).eval().to(device)\n\nmatcher = LightGlue(\n    features='aliked',\n    depth_confidence=0.92,        # slightly relaxed\n    width_confidence=0.92\n).eval().to(device)\n\ndef match_pair(p1, p2):\n    try:\n        img0 = load_image(p1).to(device)\n        img1 = load_image(p2).to(device)\n\n        feats0 = extractor.extract(img0.unsqueeze(0))\n        feats1 = extractor.extract(img1.unsqueeze(0))\n\n        matches01 = matcher({\"image0\": feats0, \"image1\": feats1})\n        feats0, feats1, matches01 = [rbd(x) for x in [feats0, feats1, matches01]]\n\n        matches = matches01[\"matches\"]\n        conf = matches01[\"scores\"].detach().cpu().numpy()\n\n        if len(matches) < 8:\n            return None\n\n        kpts0 = feats0['keypoints'][matches[:, 0]].cpu().numpy()\n        kpts1 = feats1['keypoints'][matches[:, 1]].cpu().numpy()\n\n        _, mask = cv2.findHomography(\n            kpts0.reshape(-1,1,2).astype(np.float32),\n            kpts1.reshape(-1,1,2).astype(np.float32),\n            cv2.RANSAC, 4.0\n        )\n\n        if mask is None:\n            return None\n\n        good = (mask.ravel().astype(bool) & (conf > 0.15)).sum()  # ↓ threshold 0.18→0.15\n\n        if good >= 30:   # ↓ was 60 — zyada pairs pass honge\n            print(f\"   ✓ {Path(p1).name} <-> {Path(p2).name} : {good} matches\")\n            return {\"image1\": Path(p1).name, \"image2\": Path(p2).name, \"matches\": int(good)}\n    except Exception as e:\n        pass\n    return None\n\n# ---- WINDOW-BASED PAIRING (KEY CHANGE) ----\n# Har image ko agle WINDOW_SIZE images se match karo\nWINDOW_SIZE = 7   # ← yeh badha sakte ho agar time ho (GPU pe 10+ bhi try karo)\n\nsubmission_pairs = []\nfor scene_name, imgs in scenes.items():\n    imgs_sorted = sorted(imgs)  # consistent order\n    print(f\"Processing {scene_name}... ({len(imgs_sorted)} images)\")\n    for i in range(len(imgs_sorted)):\n        for j in range(i+1, min(i+1+WINDOW_SIZE, len(imgs_sorted))):\n            pair = match_pair(imgs_sorted[i], imgs_sorted[j])\n            if pair:\n                submission_pairs.append(pair)\n\n# ---- SAVE ----\ndf = pd.DataFrame(submission_pairs)\ndf = df.drop_duplicates(subset=['image1','image2']).reset_index(drop=True)\n\n# Reverse pairs bhi add karo (bidirectional)\nreverse = df.rename(columns={'image1':'image2', 'image2':'image1'})\ndf = pd.concat([df, reverse]).drop_duplicates(subset=['image1','image2']).reset_index(drop=True)\n\ndf.to_csv('submission.csv', index=False)\nprint(f\"\\n✅ Final Pairs: {len(df)}\")\nprint(\"submission.csv ready!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-20T12:14:51.181804Z","iopub.execute_input":"2026-05-20T12:14:51.183014Z","iopub.status.idle":"2026-05-20T13:27:04.226350Z","shell.execute_reply.started":"2026-05-20T12:14:51.182964Z","shell.execute_reply":"2026-05-20T13:27:04.225574Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport pandas as pd\n\n# submission_pairs already memory mein hai\ndf.to_csv('/kaggle/working/submission.csv', index=False)\n\nprint(f\"✅ Saved! Total pairs: {len(df)}\")\nprint(f\"📁 Size: {os.path.getsize('/kaggle/working/submission.csv')} bytes\")\nprint(pd.read_csv('/kaggle/working/submission.csv').head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-20T13:50:49.574716Z","iopub.execute_input":"2026-05-20T13:50:49.575560Z","iopub.status.idle":"2026-05-20T13:50:49.639020Z","shell.execute_reply.started":"2026-05-20T13:50:49.575521Z","shell.execute_reply":"2026-05-20T13:50:49.638075Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}