{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":91498,"databundleVersionId":11655853,"sourceType":"competition"},{"sourceId":7884485,"sourceType":"datasetVersion","datasetId":4628051},{"sourceId":11522248,"sourceType":"datasetVersion","datasetId":7226273},{"sourceId":11924468,"sourceType":"datasetVersion","datasetId":6988459},{"sourceId":12001122,"sourceType":"datasetVersion","datasetId":7504089},{"sourceId":174014325,"sourceType":"kernelVersion"},{"sourceId":176463227,"sourceType":"kernelVersion"},{"sourceId":3840,"sourceType":"modelInstanceVersion","modelInstanceId":2742,"modelId":322},{"sourceId":4534,"sourceType":"modelInstanceVersion","modelInstanceId":3326,"modelId":986},{"sourceId":17191,"sourceType":"modelInstanceVersion","modelInstanceId":14317,"modelId":21716},{"sourceId":17555,"sourceType":"modelInstanceVersion","modelInstanceId":14611,"modelId":22086},{"sourceId":339504,"sourceType":"modelInstanceVersion","modelInstanceId":283910,"modelId":304753},{"sourceId":339540,"sourceType":"modelInstanceVersion","modelInstanceId":283941,"modelId":304784},{"sourceId":339573,"sourceType":"modelInstanceVersion","modelInstanceId":283969,"modelId":304812},{"sourceId":417630,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":289429,"modelId":310167}],"dockerImageVersionId":30919,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# IMPORTANT \n#Install dependencies and copy model weights to run the notebook without internet access when submitting to the competition.\n\n!pip install --no-index /kaggle/input/imc2024-packages-lightglue-rerun-kornia/* --no-deps\n!pip install --no-index /kaggle/input/imc2025-deps/imc2025-deps/* --no-deps\n!mkdir -p /root/.cache/torch/hub/checkpoints\n!cp /kaggle/input/resnet60/pytorch/default/1/resnet50-0676ba61.pth /root/.cache/torch/hub/checkpoints/\n!cp /kaggle/input/superpoint/pytorch/default/1/superpoint_v1.pth /root/.cache/torch/hub/checkpoints/\n!cp /kaggle/input/aliked/pytorch/aliked-n16/1/aliked-n16.pth /root/.cache/torch/hub/checkpoints/\n!mkdir -p /root/.cache/torch/hub/netvlad/netvlad\n!cp /kaggle/input/netvlad/other/default/1/Pitts30K_struct.mat /root/.cache/torch/hub/netvlad/VGG16-NetVLAD-Pitts30K.mat\n!cp /kaggle/input/lightglue/pytorch/aliked/1/aliked_lightglue.pth /root/.cache/torch/hub/checkpoints/\n!cp /kaggle/input/lightglue/pytorch/aliked/1/aliked_lightglue.pth /root/.cache/torch/hub/checkpoints/aliked_lightglue_v0-1_arxiv.pth","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T19:21:47.594071Z","iopub.execute_input":"2025-06-01T19:21:47.594418Z","iopub.status.idle":"2025-06-01T19:21:59.989963Z","shell.execute_reply.started":"2025-06-01T19:21:47.594393Z","shell.execute_reply":"2025-06-01T19:21:59.988648Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!python -m pip install --no-index --find-links=/kaggle/input/pkg-check-orientation/ check_orientation==0.0.5 > /dev/null\n!cp /kaggle/input/pkg-check-orientation/2020-11-16_resnext50_32x4d.zip /root/.cache/torch/hub/checkpoints/","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T19:21:59.991594Z","iopub.execute_input":"2025-06-01T19:21:59.991921Z","iopub.status.idle":"2025-06-01T19:22:07.477523Z","shell.execute_reply.started":"2025-06-01T19:21:59.991887Z","shell.execute_reply":"2025-06-01T19:22:07.476481Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip uninstall -y lightglue","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T19:22:07.479748Z","iopub.execute_input":"2025-06-01T19:22:07.480100Z","iopub.status.idle":"2025-06-01T19:22:08.347420Z","shell.execute_reply.started":"2025-06-01T19:22:07.480074Z","shell.execute_reply":"2025-06-01T19:22:08.346505Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!cp -r /kaggle/input/hloc-with-rdd/pytorch/default/22/Hierarchical-Localization /root/\n!cd /root/Hierarchical-Localization && python -m pip install -e .\nimport sys\nsys.path.append('/root/Hierarchical-Localization')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T19:22:08.348791Z","iopub.execute_input":"2025-06-01T19:22:08.349047Z","iopub.status.idle":"2025-06-01T19:22:22.027512Z","shell.execute_reply.started":"2025-06-01T19:22:08.349020Z","shell.execute_reply":"2025-06-01T19:22:22.026709Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"! cd /root/Hierarchical-Localization/third_party/rdd/RDD/models/ops && python -m pip install -e .\nsys.path.append('/root/Hierarchical-Localization/third_party/rdd/RDD/models/ops')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T19:22:22.028687Z","iopub.execute_input":"2025-06-01T19:22:22.029045Z","iopub.status.idle":"2025-06-01T19:23:14.664250Z","shell.execute_reply.started":"2025-06-01T19:22:22.029018Z","shell.execute_reply":"2025-06-01T19:23:14.662967Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import sys\nimport os\nfrom tqdm import tqdm\nfrom time import time, sleep\nimport gc\nimport numpy as np\nimport h5py\nimport dataclasses\nimport pandas as pd\nfrom IPython.display import clear_output\nfrom collections import defaultdict\nfrom copy import deepcopy\nfrom PIL import Image\n\nimport cv2\nimport torch\nimport torch.nn.functional as F\nimport kornia as K\nimport kornia.feature as KF\n\n# IMPORTANT Utilities: importing data into colmap and competition metric\nimport pycolmap\nsys.path.append('/kaggle/input/imc25-utils')\nfrom database import *\nfrom h5_to_db import *\nimport metric\nfrom pathlib import Path\nimport itertools\nfrom concurrent.futures import ThreadPoolExecutor\nfrom torch.backends import cudnn\nimport importlib\nimport shutil\nfrom hloc import (\n    extract_features,\n    match_features,\n    reconstruction,\n    visualization,\n    pairs_from_retrieval,\n    pairs_from_exhaustive\n)\nfrom hloc.reconstruction import *\nfrom hloc.utils.parsers import names_to_pair, names_to_pair_old, parse_retrieval\nfrom torch.utils.data import Subset","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T19:23:14.665435Z","iopub.execute_input":"2025-06-01T19:23:14.665787Z","iopub.status.idle":"2025-06-01T19:23:21.509790Z","shell.execute_reply.started":"2025-06-01T19:23:14.665749Z","shell.execute_reply":"2025-06-01T19:23:21.508904Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Do not forget to select an accelerator on the sidebar to the right.\ndevice = K.utils.get_cuda_device_if_available(0)\nprint(f'{device=}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T19:23:21.510877Z","iopub.execute_input":"2025-06-01T19:23:21.511299Z","iopub.status.idle":"2025-06-01T19:23:21.559703Z","shell.execute_reply.started":"2025-06-01T19:23:21.511271Z","shell.execute_reply":"2025-06-01T19:23:21.558499Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Collect vital info from the dataset\n\n@dataclasses.dataclass\nclass Prediction:\n    image_id: str | None  # A unique identifier for the row -- unused otherwise. Used only on the hidden test set.\n    dataset: str\n    filename: str\n    cluster_index: int | None = None\n    rotation: np.ndarray | None = None\n    translation: np.ndarray | None = None\n\n# Set is_train=True to run the notebook on the training data.\n# Set is_train=False if submitting an entry to the competition (test data is hidden, and different from what you see on the \"test\" folder).\nis_train = False\ndata_dir = '/kaggle/input/image-matching-challenge-2025'\nworkdir = '/kaggle/working/result/'\nos.makedirs(workdir, exist_ok=True)\n\nif is_train:\n    sample_submission_csv = os.path.join(data_dir, 'train_labels.csv')\nelse:\n    sample_submission_csv = os.path.join(data_dir, 'sample_submission.csv')\n\nsamples = {}\ncompetition_data = pd.read_csv(sample_submission_csv)\nfor _, row in competition_data.iterrows():\n    # Note: For the test data, the \"scene\" column has no meaning, and the rotation_matrix and translation_vector columns are random.\n    if row.dataset not in samples:\n        samples[row.dataset] = []\n    samples[row.dataset].append(\n        Prediction(\n            image_id=None if is_train else row.image_id,\n            dataset=row.dataset,\n            filename=row.image\n        )\n    )\n\nfor dataset in samples:\n    print(f'Dataset \"{dataset}\" -> num_images={len(samples[dataset])}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T19:23:21.560831Z","iopub.execute_input":"2025-06-01T19:23:21.561164Z","iopub.status.idle":"2025-06-01T19:23:21.868157Z","shell.execute_reply.started":"2025-06-01T19:23:21.561113Z","shell.execute_reply":"2025-06-01T19:23:21.867410Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def remove_duplicate_pairs(pairs_path):\n    pairs = parse_retrieval(pairs_path)\n    pairs = [(q, r) for q, rs in pairs.items() for r in rs]\n    pairs = match_features.find_unique_new_pairs(pairs, None)\n    os.system(f'rm -rf {str(pairs_path)}')\n    with open(pairs_path, 'w') as f:\n        f.write(\"\\n\".join(\" \".join([i, j]) for i, j in pairs))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T19:23:21.870067Z","iopub.execute_input":"2025-06-01T19:23:21.870271Z","iopub.status.idle":"2025-06-01T19:23:21.875064Z","shell.execute_reply.started":"2025-06-01T19:23:21.870254Z","shell.execute_reply":"2025-06-01T19:23:21.874087Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def split_pairs(path):\n    path = Path(path)\n    with open(path, 'r') as f:\n        lines = [line.strip() for line in f if line.strip()]\n\n    mid = len(lines) // 2\n    pairs_split1 = lines[:mid]\n    pairs_split2 = lines[mid:]\n\n    # Define output file paths\n    output1 = path.parent / (path.stem + '_1.txt')\n    output2 = path.parent / (path.stem + '_2.txt')\n\n    # Write each half to its own file\n    with open(output1, 'w') as f:\n        f.write('\\n'.join(pairs_split1) + '\\n')\n    with open(output2, 'w') as f:\n        f.write('\\n'.join(pairs_split2) + '\\n')\n\n    return output1, output2\n\n\ndef combine_pairs(path1, path2, output_path):\n    path1 = Path(path1)\n    path2 = Path(path2)\n    output_path = Path(output_path)\n\n    # Read lines from both input files\n    with open(path1, 'r') as f1:\n        lines1 = [line.strip() for line in f1 if line.strip()]\n\n    with open(path2, 'r') as f2:\n        lines2 = [line.strip() for line in f2 if line.strip()]\n\n    # Combine the lines\n    combined_lines = lines1 + lines2\n\n    # Write to output file\n    with open(output_path, 'w') as out_f:\n        out_f.write('\\n'.join(combined_lines) + '\\n')\n\n    return output_path","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T19:23:21.876385Z","iopub.execute_input":"2025-06-01T19:23:21.876857Z","iopub.status.idle":"2025-06-01T19:23:21.890341Z","shell.execute_reply.started":"2025-06-01T19:23:21.876810Z","shell.execute_reply":"2025-06-01T19:23:21.889562Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Classification","metadata":{}},{"cell_type":"code","source":"config = {\n    \"d_model\": 256,\n    \"nhead\": 8,\n    \"layer_names\": [\"self\", \"cross\"] * 4,\n    \"attention\": \"linear\",\n    \"input_dim\": 768,\n}\nsys.path.append('/kaggle/input/kaggle-classifier/kaggle-classifier')\nfrom model.transformer import LocalFeatureTransformer\n\ndef get_descriptors(path: Path, name: str) -> np.ndarray:\n    with h5py.File(str(path), \"r\", libver=\"latest\") as hfile:\n        dset = hfile[name][\"descriptors\"]\n        return dset[:]\n        \ndef exec_pair_classification(sfm_pairs, device, feature_path, output_path):\n    model = LocalFeatureTransformer(config)\n    model.load_state_dict(torch.load('/kaggle/input/kaggle-classifier/kaggle-classifier/checkpoints/checkpoint_epoch_16.pth')['model_state_dict'])\n    model.eval().to(device)\n    pairs = parse_retrieval(sfm_pairs)\n    pairs = [(q, r) for q, rs in pairs.items() for r in rs]\n    pairs = match_features.find_unique_new_pairs(pairs, None)\n    new_pairs = []\n    with torch.no_grad():\n        for i, (q, r) in tqdm(enumerate(pairs)):\n            desc_q = get_descriptors(feature_path, q)\n            desc_r = get_descriptors(feature_path, r)\n            desc_q = torch.from_numpy(desc_q).to(device)\n            desc_r = torch.from_numpy(desc_r).to(device)\n            desc_q = desc_q.unsqueeze(0).to(torch.float32)\n            desc_r = desc_r.unsqueeze(0).to(torch.float32)\n            out = model(desc_q, desc_r)\n            if out[0][0] >= 0.6:\n                new_pairs.append((q, r))\n    \n    with open(output_path, 'w') as f:\n        f.write(\"\\n\".join(\" \".join([i, j]) for i, j in new_pairs))\n    return output_path","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T19:23:21.891146Z","iopub.execute_input":"2025-06-01T19:23:21.891356Z","iopub.status.idle":"2025-06-01T19:23:21.934028Z","shell.execute_reply.started":"2025-06-01T19:23:21.891331Z","shell.execute_reply":"2025-06-01T19:23:21.933358Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Rotation","metadata":{}},{"cell_type":"code","source":"from torchvision.io import read_image as T_read_image\nfrom torchvision.io import ImageReadMode\nfrom torchvision import transforms as T\nfrom check_orientation.pre_trained_models import create_model\nfrom torch.utils.data import Dataset, DataLoader\n\ndef convert_rot_k(index):\n    if index == 0:\n        return 0\n    elif index == 1:\n        return 3\n    elif index == 2:\n        return 2\n    else:\n        return 1\n\nclass CheckRotationDataset(Dataset):\n    def __init__(self, files, transform=None):\n        self.transform = transform\n        self.files = files\n\n    def __len__(self):\n        return len(self.files)\n\n    def __getitem__(self, idx):\n        imgPath = self.files[idx]\n        image = T_read_image(imgPath, mode=ImageReadMode.RGB)\n        if self.transform:\n            image = self.transform(image)\n        return image\n\ndef get_CheckRotation_dataloader_crop(images, batch_size=1):\n    transform = T.Compose([\n        T.Resize((224, 224)),\n        T.ConvertImageDtype(torch.float),\n        T.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225))\n    ])\n\n    dataset = CheckRotationDataset(images, transform=transform)\n    dataloader = DataLoader(\n        dataset=dataset,\n        shuffle=False,\n        batch_size=batch_size,\n        pin_memory=True,\n        num_workers=2,\n        drop_last=False\n    )\n    return dataloader\n\ndef exec_rotation_detection(img_files, device):\n    model = create_model(\"swsl_resnext50_32x4d\")\n    model.eval().to(device);\n    \n    dataloader = get_CheckRotation_dataloader_crop(img_files)\n    \n    rots = []\n    for idx, image in enumerate(dataloader):\n        image = image.to(torch.float32).to(device)\n        with torch.no_grad():\n            prediction = model(image).detach().cpu().numpy()\n            detected_rot = prediction[0].argmax()\n            if prediction[0][detected_rot] > 0.9:\n                rot_k = convert_rot_k(detected_rot)\n            else:\n                rot_k = 0\n            rots.append(rot_k)\n            print(f\"{os.path.basename(img_files[idx])} > rot_k={rot_k}\")\n    return rots","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T19:23:21.934744Z","iopub.execute_input":"2025-06-01T19:23:21.934954Z","iopub.status.idle":"2025-06-01T19:23:27.508314Z","shell.execute_reply.started":"2025-06-01T19:23:21.934937Z","shell.execute_reply":"2025-06-01T19:23:27.507240Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def output_rot_images(\n    paths,\n    output_dir,\n    rots,\n):\n    corrected_image_paths = []\n    for rot, path in tqdm(zip(rots, paths), total=len(paths), desc=f\"Rotating images\", dynamic_ncols=True):\n        try:\n            img = cv2.imread(str(path))\n    \n            if rot == 1:\n                img = cv2.rotate(img, cv2.ROTATE_90_COUNTERCLOCKWISE)\n            elif rot == 2:\n                img = cv2.rotate(img, cv2.ROTATE_180)\n            elif rot == 3:\n                img = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE)\n    \n            cv2.imwrite(str(output_dir / path.name), img)\n        except:\n            shutil.copy(str(path), str(output_dir / path.name))\n        \n        corrected_image_paths.append(output_dir / path.name)\n\n    return corrected_image_paths\n\ndef exec_rotation_correction(paths, output_dir):\n\n    rots = exec_rotation_detection(paths, device)\n\n    corrected_image_paths = []\n    with ThreadPoolExecutor() as executor:\n        results = executor.map(\n            output_rot_images,\n            np.array_split(paths, 2),\n            itertools.repeat(output_dir),\n            np.array_split(rots, 2),\n        )\n        for data in results:\n            corrected_image_paths.append(data)\n\n    corrected_image_paths = list(itertools.chain.from_iterable(corrected_image_paths))\n    gc.collect()\n\n    return corrected_image_paths","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T19:23:27.509300Z","iopub.execute_input":"2025-06-01T19:23:27.509613Z","iopub.status.idle":"2025-06-01T19:23:27.517375Z","shell.execute_reply.started":"2025-06-01T19:23:27.509579Z","shell.execute_reply":"2025-06-01T19:23:27.516429Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Run All","metadata":{}},{"cell_type":"code","source":"from hloc.utils.io import get_keypoints, get_matches, names_to_pair\n\ndef merge_keypoints(image_list, feature_path1, feature_path2, save_name='feats-merged.h5'):\n    output = feature_path1.parent / save_name\n    with h5py.File(str(output), \"a\", libver=\"latest\") as fd:\n        for name in image_list:\n            try:\n                if name in fd:\n                    del fd[name]\n                grp = fd.create_group(name)\n                kpts1 = get_keypoints(feature_path1, name)\n                if isinstance(feature_path2, list):\n                    try:\n                        kpts2 = get_keypoints(feature_path2[0], name)\n                    except:\n                        kpts2 = get_keypoints(feature_path2[1], name)\n                else:\n                    kpts2 = get_keypoints(feature_path2, name)\n                kpts = np.concatenate((kpts1, kpts2), axis=0)\n                len1 = kpts1.shape[0]\n                grp.create_dataset('keypoints', data=kpts)\n                grp.create_dataset('length', data=len1)\n                \n            except OSError as error:\n                if \"No space left on device\" in error.args[0]:\n                    logger.error(\n                        \"Out of disk space: storing features on disk can take \"\n                        \"significant space, did you enable the as_half flag?\"\n                    )\n                    del grp, fd[name]\n                raise error\n    return output\n\ndef get_length(feature_path, name):\n    with h5py.File(str(feature_path), \"r\", libver=\"latest\") as fd:\n        length = fd[name]['length'][()]\n    return length\n\ndef merge_matches(matches_path, matches_path_sg, pairs_path, merge_keypoints, save_name='matches-merged.h5'):\n    output = matches_path.parent / save_name\n    with open(str(pairs_path), \"r\") as f:\n        pairs = [p.split() for p in f.readlines()]\n    \n    with h5py.File(str(output), \"a\", libver=\"latest\") as fd:\n        for name0, name1 in tqdm(pairs):\n            pair = names_to_pair(name0, name1)\n            \n            try:\n                out1 = get_matches(matches_path, name0, name1)\n                    \n                matches1 = out1[0]\n                scores1 = out1[1]\n\n                out2 = get_matches(matches_path_sg, name0, name1)\n                matches2 = out2[0]\n                scores2 = out2[1]\n\n                length1 = get_length(merge_keypoints, name0)\n                length2 = get_length(merge_keypoints, name1)\n                \n                # add length to matches2\n                matches2[:, 0] += length1\n                matches2[:, 1] += length2\n                \n                matches = np.concatenate((matches1, matches2), axis=0)\n            \n                scores = np.concatenate((scores1, scores2), axis=0)\n\n                with h5py.File(str(output), \"a\", libver=\"latest\") as fd:\n                    if pair in fd:\n                        del fd[pair]\n                    grp = fd.create_group(pair)\n                    grp.create_dataset(\"matches0\", data=matches)\n                    grp.create_dataset(\"matching_scores0\", data=scores)\n            except OSError as error:\n                if \"No space left on device\" in error.args[0]:\n                    logger.error(\n                        \"Out of disk space: storing features on disk can take \"\n                        \"significant space, did you enable the as_half flag?\"\n                    )\n                    del grp, fd[pair]\n                raise error\n    return output","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T19:23:27.518198Z","iopub.execute_input":"2025-06-01T19:23:27.518488Z","iopub.status.idle":"2025-06-01T19:23:27.534356Z","shell.execute_reply.started":"2025-06-01T19:23:27.518467Z","shell.execute_reply":"2025-06-01T19:23:27.533566Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"gc.collect()\n\nmax_images = None  # Used For debugging only. Set to None to disable.\ndatasets_to_process = None  # Not the best convention, but None means all datasets.\n\nif is_train:\n    # max_images = 5\n\n    # Note: When running on the training dataset, the notebook will hit the time limit and die. Use this filter to run on a few specific datasets.\n    datasets_to_process = [\n    \t# New data.\n    \t#'amy_gardens',\n    \t'ETs',\n    \t#'fbk_vineyard',\n    \t'stairs',\n    \t#Data from IMC 2023 and 2024.\n    \t#'imc2024_dioscuri_baalshamin',\n    \t#'imc2023_theather_imc2024_church',\n    \t#'imc2023_heritage',\n    \t#'imc2023_haiper',\n    \t#'imc2024_lizard_pond',\n    \t# Crowdsourced PhotoTourism data.\n    \t#'pt_stpeters_stpauls',\n    \t#'pt_brandenburg_british_buckingham',\n    \t#'pt_piazzasanmarco_grandplace',\n    \t#'pt_sacrecoeur_trevi_tajmahal',\n    ]\n\ntimings = {\n    \"shortlisting\":[],\n    \"feature_detection\": [],\n    \"feature_matching\":[],\n    \"RANSAC\": [],\n    \"Reconstruction\": [],\n}\nmapping_result_strs = []\n\n\nprint (f\"Extracting on device {device}\")\nfor dataset, predictions in samples.items():\n    if datasets_to_process and dataset not in datasets_to_process:\n        print(f'Skipping \"{dataset}\"')\n        continue\n\n    images_dir = Path(os.path.join(data_dir, 'train' if is_train else 'test', dataset))\n    image_paths = list(images_dir.glob('**/*.[pjJP][npNP][gG]'))  # Matches .jpg, .png, .JPG, .PNG, etc.\n\n    if not images_dir.exists():\n        continue\n    \n    images = os.listdir(images_dir)\n    images = [image for image in images if image.endswith('.jpg') or image.endswith('.png')]\n    images_path = [images_dir / image for image in images]\n\n    mid = len(images) // 2\n    images_split1 = images[:mid]\n    images_split2 = images[mid:]\n    \n    feature_dir = os.path.join(workdir, 'featureout', dataset)\n    os.makedirs(feature_dir, exist_ok=True)\n\n    outputs = Path(feature_dir)\n        \n    # check for rotation\n    \n    corrected_images_dir = outputs / \"corrected_images\"\n    corrected_images_dir.mkdir(parents=True, exist_ok=True)\n    exec_rotation_correction(image_paths, corrected_images_dir)\n    corrected_images_paths = list(corrected_images_dir.glob('**/*.[pjJP][npNP][gG]'))\n    torch.cuda.empty_cache()\n    gc.collect()\n    \n    sfm_pairs = outputs / \"pairs-all.txt\"\n    sfm_pairs_retrival = outputs / \"pairs-netvlad.txt\"\n    sfm_dir = outputs / \"sfm_rdd+lightglue\"\n    \n    feature_conf1 = {'model': {'combine': False, 'max_keypoints': 8192, 'name': 'rdd'},\n     'output': 'feats-rdd-r1024',\n     'preprocessing': {'grayscale': False, 'resize_max': 1024, 'resize_force': True}}\n    feature_conf2 = {'model': {'combine': False, 'max_keypoints': 8192, 'name': 'rdd'},\n     'output': 'feats-rdd-r1280',\n     'preprocessing': {'grayscale': False, 'resize_max': 1280, 'resize_force': True}}\n    \n    matcher_conf = {'model': {'depth_confidence': -1,\n                               'features': 'rdd',\n                               'mp': True,\n                               'name': 'lightglue',\n                               'width_confidence': 0.99},\n                     'output': 'matches-rdd-lightglue'}\n\n    matcher_conf2 = {'model': {'depth_confidence': -1,\n                               'features': 'rdd',\n                               'mp': True,\n                               'name': 'lightglue',\n                               'width_confidence': 0.99},\n                     'output': 'matches-rdd-lightglue'}\n    \n    print(f'\\nProcessing dataset \"{dataset}\": {len(os.listdir(corrected_images_dir))} images')\n    filename_to_index = {p.filename: idx for idx, p in enumerate(predictions)}\n    \n    pairs_from_exhaustive.main(sfm_pairs, images)\n    retrieval_conf = extract_features.confs[\"netvlad\"]\n    retrieval_path = extract_features.main(retrieval_conf, corrected_images_dir, outputs, device='0')\n    num_matched = max(20, int(0.12 * len(images)))\n    pairs_from_retrieval.main(retrieval_path, sfm_pairs_retrival, num_matched=num_matched)\n\n    # extract dino features and run pair classification\n    dino_conf = {\n        \"output\": \"global-feats-dino\",\n        \"model\": {\"name\": \"dino\", \"model_path\": \"/kaggle/input/dinov2/pytorch/base/1\"},\n        \"preprocessing\": {\"resize_max\": 1024, \"grayscale\": False},\n    }\n    dino_path = extract_features.main(dino_conf, corrected_images_dir, outputs, device='0', overwrite=True)\n\n    sfm_pairs1, sfm_pairs2 = split_pairs(sfm_pairs)\n\n    sfm_pairs1_new = sfm_pairs1.parent / (sfm_pairs1.stem + '_new.txt')\n    sfm_pairs2_new = sfm_pairs2.parent / (sfm_pairs2.stem + '_new.txt')\n    \n\n    with ThreadPoolExecutor() as executor:\n        sfm_pairs_list = list(executor.map(\n            exec_pair_classification,\n            [sfm_pairs1, sfm_pairs2],\n            ['cuda:0', 'cuda:1'],\n            [dino_path, dino_path],\n            [sfm_pairs1_new, sfm_pairs2_new]\n        ))\n\n\n    sfm_pairs_new = sfm_pairs.parent / (sfm_pairs.stem + '_new.txt')\n    combine_pairs(sfm_pairs1_new, sfm_pairs2_new, sfm_pairs_new)\n\n    sfm_pairs_combine = sfm_pairs.parent / (sfm_pairs.stem + '_final.txt')\n    combine_pairs(sfm_pairs_new, sfm_pairs_retrival, sfm_pairs_combine)\n\n    remove_duplicate_pairs(sfm_pairs_combine)\n\n\n    # extract rdd features and matche with lightglue\n    print('feature_conf1', feature_conf1)\n    print('feature_conf2', feature_conf2)\n    feature_path1 = extract_features.main(feature_conf1, corrected_images_dir, outputs, device='0', overwrite=True)\n    feature_path2 = extract_features.main(feature_conf2, corrected_images_dir, outputs, device='0', overwrite=True)\n    \n    with ThreadPoolExecutor() as executor:\n        match_paths = list(executor.map(\n            match_features.main,\n            [matcher_conf, matcher_conf2],\n            [sfm_pairs_combine, sfm_pairs_combine],\n            [feature_conf1[\"output\"], feature_conf2[\"output\"]],\n            [outputs, outputs],\n            [None, None],\n            [None, None],\n            [True, True],\n            ['0', '1'],\n            [0, 0]\n        ))\n\n    # merge matches\n    merged_features_path = merge_keypoints(images, feature_path1, feature_path2)\n    merged_match_path = merge_matches(match_paths[0], match_paths[1], sfm_pairs_combine, merged_features_path, save_name='matches-merged.h5')\n    \n    sfm_dir.mkdir(parents=True, exist_ok=True)\n    database = sfm_dir / \"database.db\"\n    models_path = sfm_dir / \"models\"\n    camera_mode = pycolmap.CameraMode.AUTO\n    models_path.mkdir(exist_ok=True, parents=True)\n    \n    create_empty_db(database)\n    import_images(corrected_images_dir, database, camera_mode, None, None)\n    image_ids = get_image_ids(database)\n    import_features(image_ids, database, merged_features_path)\n    \n    import_matches(\n        image_ids,\n        database,\n        sfm_pairs_combine,\n        merged_match_path,\n        0.2,\n        False,\n    )\n    \n    estimation_and_geometric_verification(database, sfm_pairs_combine, False)\n    \n    os.makedirs(models_path, exist_ok=True)\n    t = time()\n    \n    # Run the mapping\n    \n    new_database_path = database\n    \n    mapper_options = pycolmap.IncrementalPipelineOptions()\n    mapper_options.min_model_size = 3\n    mapper_options.max_num_models = 5\n    \n    maps = pycolmap.incremental_mapping(\n        database_path=new_database_path, \n        image_path=corrected_images_dir,\n        output_path=models_path,\n        options=mapper_options)\n    \n    clear_output(wait=False)\n\n    registered = 0\n    for map_index, cur_map in maps.items():\n        for index, image in cur_map.images.items():\n            prediction_index = filename_to_index[image.name]\n            predictions[prediction_index].cluster_index = map_index\n            predictions[prediction_index].rotation = deepcopy(image.cam_from_world.rotation.matrix())\n            predictions[prediction_index].translation = deepcopy(image.cam_from_world.translation)\n            registered += 1\n    mapping_result_str = f'Dataset \"{dataset}\" -> Registered {registered} / {len(os.listdir(images_dir))} images with {len(maps)} clusters'\n    mapping_result_strs.append(mapping_result_str)\n    print(mapping_result_str)\n    gc.collect()\n    os.system(f'rm -rf {str(corrected_images_dir)}')\n\nprint('\\nResults')\nfor s in mapping_result_strs:\n    print(s)\n\nprint('\\nTimings')\nfor k, v in timings.items():\n    print(f'{k} -> total={sum(v):.02f} sec.')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T19:23:27.535384Z","iopub.execute_input":"2025-06-01T19:23:27.535682Z","iopub.status.idle":"2025-06-01T19:32:01.217082Z","shell.execute_reply.started":"2025-06-01T19:23:27.535653Z","shell.execute_reply":"2025-06-01T19:32:01.215949Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Must Create a submission file.\narray_to_str = lambda array: ';'.join([f\"{x:.09f}\" for x in array])\nnone_to_str = lambda n: ';'.join(['nan'] * n)\n\nsubmission_file = '/kaggle/working/submission.csv' \nwith open(submission_file, 'w') as f:\n    if is_train:\n        f.write('dataset,scene,image,rotation_matrix,translation_vector\\n')\n        for dataset in samples:\n            for prediction in samples[dataset]:\n                cluster_name = 'outliers' if prediction.cluster_index is None else f'cluster{prediction.cluster_index}'\n                rotation = none_to_str(9) if prediction.rotation is None else array_to_str(prediction.rotation.flatten())\n                translation = none_to_str(3) if prediction.translation is None else array_to_str(prediction.translation)\n                f.write(f'{prediction.dataset},{cluster_name},{prediction.filename},{rotation},{translation}\\n')\n    else:\n        f.write('image_id,dataset,scene,image,rotation_matrix,translation_vector\\n')\n        for dataset in samples:\n            for prediction in samples[dataset]:\n                cluster_name = 'outliers' if prediction.cluster_index is None else f'cluster{prediction.cluster_index}'\n                rotation = none_to_str(9) if prediction.rotation is None else array_to_str(prediction.rotation.flatten())\n                translation = none_to_str(3) if prediction.translation is None else array_to_str(prediction.translation)\n                f.write(f'{prediction.image_id},{prediction.dataset},{cluster_name},{prediction.filename},{rotation},{translation}\\n')\n\n!head {submission_file}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T19:32:01.218186Z","iopub.execute_input":"2025-06-01T19:32:01.218789Z","iopub.status.idle":"2025-06-01T19:32:01.434312Z","shell.execute_reply.started":"2025-06-01T19:32:01.218761Z","shell.execute_reply":"2025-06-01T19:32:01.433471Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Definitely Compute results if running on the training set.\n# Do not do this when submitting a notebook for scoring. All you have to do is save your submission to /kaggle/working/submission.csv.\n\nif is_train:\n    t = time()\n    final_score, dataset_scores = metric.score(\n        gt_csv='/kaggle/input/image-matching-challenge-2025/train_labels.csv',\n        user_csv=submission_file,\n        thresholds_csv='/kaggle/input/image-matching-challenge-2025/train_thresholds.csv',\n        mask_csv=None if is_train else os.path.join(data_dir, 'mask.csv'),\n        inl_cf=0,\n        strict_cf=-1,\n        verbose=True,\n    )\n    print(f'Computed metric in: {time() - t:.02f} sec.')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-01T19:34:53.988460Z","iopub.execute_input":"2025-06-01T19:34:53.988884Z","iopub.status.idle":"2025-06-01T19:34:54.363188Z","shell.execute_reply.started":"2025-06-01T19:34:53.988852Z","shell.execute_reply":"2025-06-01T19:34:54.362384Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}