{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":91498,"databundleVersionId":11655853,"sourceType":"competition"}],"dockerImageVersionId":31012,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\n#import os\n#for dirname, _, filenames in os.walk('/kaggle/input'):\n#    for filename in filenames:\n#        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-04-20T09:00:57.900226Z","iopub.execute_input":"2025-04-20T09:00:57.900575Z","iopub.status.idle":"2025-04-20T09:00:58.229094Z","shell.execute_reply.started":"2025-04-20T09:00:57.900548Z","shell.execute_reply":"2025-04-20T09:00:58.228255Z"},"_kg_hide-input":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Folder Image Loader + Viewer","metadata":{}},{"cell_type":"code","source":"import os\nimport matplotlib.pyplot as plt\nfrom PIL import Image\n\n# Define dataset path and folder to load\nbase_path = \"/kaggle/input/image-matching-challenge-2025/test\"\nfolder_name = \"ETs\"  # Change this to any folder you want to explore\nfolder_path = os.path.join(base_path, folder_name)\n\n# Get all PNG image paths in the folder\nimage_paths = sorted([f for f in os.listdir(folder_path) if f.endswith('.png')])\n\nprint(f\"Found {len(image_paths)} images in folder: {folder_name}\")\n\n# Lazy load and preview first 5 images (adjust if needed)\nn_show = 5\nplt.figure(figsize=(15, 3))\nfor i, img_name in enumerate(image_paths[:n_show]):\n    img_path = os.path.join(folder_path, img_name)\n    img = Image.open(img_path)\n    \n    plt.subplot(1, n_show, i + 1)\n    plt.imshow(img)\n    plt.title(img_name, fontsize=8)\n    plt.axis(\"off\")\nplt.suptitle(f\"Preview: {folder_name}\", fontsize=14)\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T09:00:58.230465Z","iopub.execute_input":"2025-04-20T09:00:58.230900Z","iopub.status.idle":"2025-04-20T09:00:59.026604Z","shell.execute_reply.started":"2025-04-20T09:00:58.230878Z","shell.execute_reply":"2025-04-20T09:00:59.025677Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Extract Image Embeddings (CLIP, memory-safe per folder)","metadata":{}},{"cell_type":"code","source":"!pip install open-clip-torch\n!pip install tqdm\n!pip install transformers\n!pip install --quiet git+https://github.com/openai/CLIP.git","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T09:00:59.027808Z","iopub.execute_input":"2025-04-20T09:00:59.028199Z","iopub.status.idle":"2025-04-20T09:02:48.806965Z","shell.execute_reply.started":"2025-04-20T09:00:59.028169Z","shell.execute_reply":"2025-04-20T09:02:48.805476Z"},"_kg_hide-output":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"##  Load CLIP Model and Extract Features","metadata":{}},{"cell_type":"code","source":"import torch\nimport clip\nfrom PIL import Image\nfrom tqdm import tqdm\nimport os\n\n# Load CLIP model (ViT-B/32 is a good balance of speed and performance)\ndevice = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nmodel, preprocess = clip.load(\"ViT-B/32\", device=device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T09:02:48.811106Z","iopub.execute_input":"2025-04-20T09:02:48.811500Z","iopub.status.idle":"2025-04-20T09:03:08.789682Z","shell.execute_reply.started":"2025-04-20T09:02:48.811468Z","shell.execute_reply":"2025-04-20T09:03:08.788741Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Extract Image Embeddings from One Folder","metadata":{}},{"cell_type":"code","source":"def extract_clip_embeddings(folder_path, image_names):\n    embeddings = []\n    names = []\n\n    for name in tqdm(image_names, desc=\"Embedding images\"):\n        path = os.path.join(folder_path, name)\n        image = preprocess(Image.open(path)).unsqueeze(0).to(device)\n\n        with torch.no_grad():\n            embedding = model.encode_image(image).cpu().squeeze(0)\n        \n        embeddings.append(embedding)\n        names.append(name)\n\n    embeddings_tensor = torch.stack(embeddings)\n    return names, embeddings_tensor\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T09:03:08.790753Z","iopub.execute_input":"2025-04-20T09:03:08.791125Z","iopub.status.idle":"2025-04-20T09:03:08.797329Z","shell.execute_reply.started":"2025-04-20T09:03:08.791103Z","shell.execute_reply":"2025-04-20T09:03:08.796464Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Run it on One Folder (ETs)","metadata":{}},{"cell_type":"code","source":"# Choose test folder\nfolder_name = \"ETs\"\nfolder_path = f\"/kaggle/input/image-matching-challenge-2025/test/{folder_name}\"\nimage_names = sorted([f for f in os.listdir(folder_path) if f.endswith('.png')])\n\n# Extract CLIP embeddings\nnames, embs = extract_clip_embeddings(folder_path, image_names)\nprint(f\"Extracted shape: {embs.shape} (num_images, 512)\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T09:03:08.798919Z","iopub.execute_input":"2025-04-20T09:03:08.799178Z","iopub.status.idle":"2025-04-20T09:03:12.396623Z","shell.execute_reply.started":"2025-04-20T09:03:08.799157Z","shell.execute_reply":"2025-04-20T09:03:12.395676Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Load CLIP Model and Extract Features\n","metadata":{}},{"cell_type":"code","source":"import os\nimport torch\nimport clip\nfrom PIL import Image\nfrom tqdm import tqdm\n\n# Load CLIP model (ViT-B/32 is lightweight)\ndevice = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nmodel, preprocess = clip.load(\"ViT-B/32\", device=device)\n\n# Pick a test folder to avoid OOM — e.g., ETs\nfolder_path = \"/kaggle/input/image-matching-challenge-2025/test/ETs\"\nimage_files = sorted([f for f in os.listdir(folder_path) if f.endswith('.png')])\n\n# Extract CLIP features (image embeddings)\nimage_features = {}\nwith torch.no_grad():\n    for image_name in tqdm(image_files, desc=\"Extracting CLIP features\"):\n        img_path = os.path.join(folder_path, image_name)\n        image = preprocess(Image.open(img_path)).unsqueeze(0).to(device)\n        features = model.encode_image(image)\n        image_features[image_name] = features.squeeze(0).cpu()  # shape: (512,)\n\nprint(f\"Extracted features for {len(image_features)} images in '{folder_path.split('/')[-1]}'\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T09:03:12.397592Z","iopub.execute_input":"2025-04-20T09:03:12.398393Z","iopub.status.idle":"2025-04-20T09:03:21.989239Z","shell.execute_reply.started":"2025-04-20T09:03:12.398370Z","shell.execute_reply":"2025-04-20T09:03:21.988321Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":" ## Scene Clustering from CLIP Features","metadata":{}},{"cell_type":"code","source":"from sklearn.cluster import DBSCAN\nfrom sklearn.preprocessing import normalize\nimport numpy as np\n\n# Stack all features into matrix\nfeature_matrix = torch.stack(list(image_features.values())).numpy()\nfeature_matrix = normalize(feature_matrix)  # L2 normalization for cosine distance\n\n# DBSCAN with cosine metric (eps controls granularity)\nclustering = DBSCAN(eps=0.3, min_samples=2, metric='cosine').fit(feature_matrix)\n\n# Get image -> cluster label mapping\nlabels = clustering.labels_  # -1 = outlier\nimage_to_cluster = dict(zip(image_features.keys(), labels))\n\n# Print clustering result\nfrom collections import Counter\nprint(Counter(labels))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T09:03:21.990150Z","iopub.execute_input":"2025-04-20T09:03:21.990504Z","iopub.status.idle":"2025-04-20T09:03:23.811620Z","shell.execute_reply.started":"2025-04-20T09:03:21.990474Z","shell.execute_reply":"2025-04-20T09:03:23.810781Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Format a Submission Entry","metadata":{}},{"cell_type":"code","source":"import pandas as pd\n\ndataset_name = 'ETs'\nsubmission_rows = []\n\nfor image_name, cluster_id in image_to_cluster.items():\n    if cluster_id == -1:\n        scene_label = 'outliers'\n        rot = ';'.join(['nan'] * 9)\n        trans = ';'.join(['nan'] * 3)\n    else:\n        scene_label = f'cluster{cluster_id + 1}'\n        rot = ';'.join(['nan'] * 9)\n        trans = ';'.join(['nan'] * 3)\n\n    submission_rows.append({\n        'dataset': dataset_name,\n        'scene': scene_label,\n        'image': image_name,\n        'rotation_matrix': rot,\n        'translation_vector': trans\n    })\n\nsubmission_df = pd.DataFrame(submission_rows)\nsubmission_df.head()\n\n# Added line to save the DataFrame to a CSV file\nsubmission_df.to_csv('submission.csv', index=False)\nsubmission_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-20T09:03:23.812500Z","iopub.execute_input":"2025-04-20T09:03:23.813157Z","iopub.status.idle":"2025-04-20T09:03:23.861558Z","shell.execute_reply.started":"2025-04-20T09:03:23.813132Z","shell.execute_reply":"2025-04-20T09:03:23.860672Z"}},"outputs":[],"execution_count":null}]}