{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","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":30918,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"### Cell 1: Install Libraries","metadata":{}},{"cell_type":"code","source":"# Import pre-installed libraries\nimport os\nimport numpy as np\nimport pandas as pd\nimport cv2\nfrom sklearn.cluster import DBSCAN\nfrom sklearn.neighbors import NearestNeighbors\nfrom tqdm import tqdm","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-04T08:01:51.801485Z","iopub.execute_input":"2025-04-04T08:01:51.801936Z","iopub.status.idle":"2025-04-04T08:01:52.874304Z","shell.execute_reply.started":"2025-04-04T08:01:51.801893Z","shell.execute_reply":"2025-04-04T08:01:52.873160Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Cell 2: Load Dataset","metadata":{}},{"cell_type":"code","source":"# Define paths\nDATASET_PATH = \"/kaggle/input/image-matching-challenge-2025\"\nTRAIN_PATH = os.path.join(DATASET_PATH, \"train\")\nTEST_PATH = os.path.join(DATASET_PATH, \"test\")\n\n# List datasets and images\ndatasets = [d for d in os.listdir(TRAIN_PATH) if os.path.isdir(os.path.join(TRAIN_PATH, d))]\nprint(f\"Datasets: {datasets}\")\n\n# Example: Load one dataset\ndataset_name = datasets[0]\ndataset_path = os.path.join(TRAIN_PATH, dataset_name)\nimage_paths = [\n    os.path.join(dataset_path, img)\n    for img in os.listdir(dataset_path)\n    if img.endswith(\".png\") and not img.startswith(\"LICENSE\")\n]\nprint(f\"Images in {dataset_name}: {len(image_paths)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-04T08:01:53.124902Z","iopub.execute_input":"2025-04-04T08:01:53.125623Z","iopub.status.idle":"2025-04-04T08:01:53.143515Z","shell.execute_reply.started":"2025-04-04T08:01:53.125579Z","shell.execute_reply":"2025-04-04T08:01:53.142368Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Cell 3: Feature Extraction with ORB","metadata":{}},{"cell_type":"code","source":"# Function to extract ORB features\ndef extract_orb_features(image_path):\n    image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)\n    orb = cv2.ORB_create()\n    keypoints, descriptors = orb.detectAndCompute(image, None)\n    return descriptors\n\n# Extract features for all images in a dataset\ndef extract_dataset_features(image_paths):\n    features = []\n    valid_image_paths = []\n    for img_path in tqdm(image_paths, desc=\"Extracting Features\"):\n        desc = extract_orb_features(img_path)\n        if desc is not None:\n            features.append(desc)\n            valid_image_paths.append(img_path)\n    return features, valid_image_paths\n\n# Extract features\nfeatures, image_paths = extract_dataset_features(image_paths)\nprint(f\"Extracted features for {len(features)} images.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-04T08:01:57.774660Z","iopub.execute_input":"2025-04-04T08:01:57.775075Z","iopub.status.idle":"2025-04-04T08:02:09.434141Z","shell.execute_reply.started":"2025-04-04T08:01:57.775038Z","shell.execute_reply":"2025-04-04T08:02:09.433030Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Cell 4: Flatten Descriptors","metadata":{}},{"cell_type":"code","source":"# Flatten descriptors into a single array\ndef flatten_descriptors(features):\n    all_desc = np.vstack(features)\n    return all_desc.astype('float32')\n\n# Flatten features\nflat_features = flatten_descriptors(features)\nprint(f\"Flattened features to shape: {flat_features.shape}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-04T08:02:09.435597Z","iopub.execute_input":"2025-04-04T08:02:09.436012Z","iopub.status.idle":"2025-04-04T08:02:09.448101Z","shell.execute_reply.started":"2025-04-04T08:02:09.435974Z","shell.execute_reply":"2025-04-04T08:02:09.447067Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Cell 5: Clustering with Approximate Nearest Neighbors","metadata":{}},{"cell_type":"code","source":"# Cluster images using approximate nearest neighbors\ndef cluster_images(features, eps=50, min_samples=2):\n    print(\"Running DBSCAN with approximate nearest neighbors...\")\n    \n    # Compute nearest neighbors\n    nbrs = NearestNeighbors(metric='euclidean').fit(features)\n    distances, indices = nbrs.kneighbors(features, n_neighbors=min_samples)\n    \n    # Assign cluster labels based on neighbor indices\n    clusters = np.full(len(features), -1)  # Initialize all as outliers\n    current_cluster = 0\n    \n    for i in range(len(features)):\n        if clusters[i] != -1:  # Already assigned\n            continue\n        \n        # Find neighbors within eps\n        neighbors = [j for j in indices[i] if distances[i][np.where(indices[i] == j)] <= eps]\n        if len(neighbors) >= min_samples:\n            clusters[i] = current_cluster\n            stack = list(neighbors)\n            \n            while stack:\n                node = stack.pop()\n                if clusters[node] == -1:\n                    clusters[node] = current_cluster\n                    new_neighbors = [j for j in indices[node] if distances[node][np.where(indices[node] == j)] <= eps]\n                    stack.extend(new_neighbors)\n            \n            current_cluster += 1\n    \n    return clusters\n\n# Cluster images\nclusters = cluster_images(flat_features)\nprint(f\"Cluster labels: {np.unique(clusters)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-04T08:02:16.861531Z","iopub.execute_input":"2025-04-04T08:02:16.861887Z","iopub.status.idle":"2025-04-04T08:02:40.156456Z","shell.execute_reply.started":"2025-04-04T08:02:16.861852Z","shell.execute_reply":"2025-04-04T08:02:40.155291Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Cell 6: Basic Structure from Motion (SfM)","metadata":{}},{"cell_type":"code","source":"# Ensure alignment between clusters and image_paths\nvalid_image_paths = []\nvalid_clusters = []\n\nfor i, img_path in enumerate(image_paths):\n    if i < len(clusters):  # Check if cluster label exists for this image\n        valid_image_paths.append(img_path)\n        valid_clusters.append(clusters[i])\n\n# Replace image_paths and clusters with the aligned versions\nimage_paths = valid_image_paths\nclusters = np.array(valid_clusters)\n\nprint(f\"Aligned {len(image_paths)} images with {len(clusters)} cluster labels.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-04T08:04:25.061794Z","iopub.execute_input":"2025-04-04T08:04:25.062219Z","iopub.status.idle":"2025-04-04T08:04:25.068746Z","shell.execute_reply.started":"2025-04-04T08:04:25.062188Z","shell.execute_reply":"2025-04-04T08:04:25.067391Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Perform basic SfM using OpenCV\ndef reconstruct_scene(image_paths, output_dir):\n    if not os.path.exists(output_dir):\n        os.makedirs(output_dir)\n    \n    # Match features between pairs of images\n    orb = cv2.ORB_create()\n    bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)\n    \n    for i in range(len(image_paths) - 1):\n        img1 = cv2.imread(image_paths[i], cv2.IMREAD_GRAYSCALE)\n        img2 = cv2.imread(image_paths[i + 1], cv2.IMREAD_GRAYSCALE)\n        \n        kp1, des1 = orb.detectAndCompute(img1, None)\n        kp2, des2 = orb.detectAndCompute(img2, None)\n        \n        if des1 is None or des2 is None:\n            continue  # Skip if descriptors are missing\n        \n        matches = bf.match(des1, des2)\n        matches = sorted(matches, key=lambda x: x.distance)\n        \n        # Extract matched points\n        pts1 = np.float32([kp1[m.queryIdx].pt for m in matches]).reshape(-1, 2)\n        pts2 = np.float32([kp2[m.trainIdx].pt for m in matches]).reshape(-1, 2)\n        \n        # Compute Essential Matrix and recover pose\n        focal = 1.0  # Assume normalized coordinates\n        pp = (0, 0)  # Principal point\n        E, mask = cv2.findEssentialMat(pts1, pts2, focal, pp, method=cv2.RANSAC, prob=0.999, threshold=1.0)\n        _, R, t, _ = cv2.recoverPose(E, pts1, pts2, focal=focal, pp=pp)\n        \n        # Save results (rotation matrix and translation vector)\n        np.save(os.path.join(output_dir, f\"pose_{i}.npy\"), {\"R\": R, \"t\": t})\n\n# Reconstruct scenes for each cluster\nfor cluster_id in np.unique(clusters):\n    if cluster_id == -1:\n        continue  # Skip outliers\n    \n    cluster_images = [image_paths[i] for i, label in enumerate(clusters) if label == cluster_id]\n    output_dir = f\"/kaggle/working/scene_{cluster_id}\"\n    reconstruct_scene(cluster_images, output_dir)\n    print(f\"Reconstructed scene {cluster_id} with {len(cluster_images)} images.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-04T08:04:32.763914Z","iopub.execute_input":"2025-04-04T08:04:32.764301Z","iopub.status.idle":"2025-04-04T08:04:32.773558Z","shell.execute_reply.started":"2025-04-04T08:04:32.764273Z","shell.execute_reply":"2025-04-04T08:04:32.772563Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Cell 7: Generate Submission File","metadata":{}},{"cell_type":"code","source":"def generate_submission(clusters, image_paths, output_path):\n    rows = []\n    for i, label in enumerate(clusters):\n        dataset = dataset_name  # Use the current dataset name\n        scene = f\"cluster{label}\" if label != -1 else \"outliers\"\n        image = os.path.basename(image_paths[i])\n        \n        # Placeholder for rotation matrix and translation vector\n        rotation_matrix = \"nan;nan;nan;nan;nan;nan;nan;nan;nan\" if label == -1 else \"0.1;0.2;0.3;0.4;0.5;0.6;0.7;0.8;0.9\"\n        translation_vector = \"nan;nan;nan\" if label == -1 else \"0.1;0.2;0.3\"\n        \n        rows.append([dataset, scene, image, rotation_matrix, translation_vector])\n    \n    df = pd.DataFrame(rows, columns=[\"dataset\", \"scene\", \"image\", \"rotation_matrix\", \"translation_vector\"])\n    df.to_csv(output_path, index=False)\n    print(f\"Submission file saved to {output_path}\")\n\n# Save submission file\ngenerate_submission(clusters, image_paths, \"/kaggle/working/submission.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-04T08:04:42.511891Z","iopub.execute_input":"2025-04-04T08:04:42.512262Z","iopub.status.idle":"2025-04-04T08:04:42.530223Z","shell.execute_reply.started":"2025-04-04T08:04:42.512234Z","shell.execute_reply":"2025-04-04T08:04:42.529128Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}