{"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":"gpu","dataSources":[{"sourceId":91498,"databundleVersionId":11655853,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":7884485,"sourceType":"datasetVersion","datasetId":4628051},{"sourceId":11217117,"sourceType":"datasetVersion","datasetId":6988459},{"sourceId":11761257,"sourceType":"datasetVersion","datasetId":7383518},{"sourceId":11766865,"sourceType":"datasetVersion","datasetId":7387144},{"sourceId":11767027,"sourceType":"datasetVersion","datasetId":7387245},{"sourceId":4534,"sourceType":"modelInstanceVersion","isSourceIdPinned":false,"modelInstanceId":3326,"modelId":986},{"sourceId":17191,"sourceType":"modelInstanceVersion","isSourceIdPinned":false,"modelInstanceId":14317,"modelId":21716},{"sourceId":17555,"sourceType":"modelInstanceVersion","isSourceIdPinned":false,"modelInstanceId":14611,"modelId":22086}],"dockerImageVersionId":30918,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## Example submission\n\nImage Matching Challenge 2025: https://www.kaggle.com/competitions/image-matching-challenge-2025\n\nThis notebook creates a simple submission using ALIKED and LightGlue, plus DINO for shortlisting, on GPU. Adapted from [last year](https://www.kaggle.com/code/oldufo/imc-2024-submission-example).\n\nRemember to select an accelerator on the sidebar to the right, and to disable internet access when submitting a notebook to the competition.","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19"}},{"cell_type":"code","source":"# Install dependencies and copy model weights to run the notebook without internet access when submitting to the competition.\n!pip install --no-index /kaggle/input/imc2024-packages-lightglue-rerun-kornia/* --no-deps\n!mkdir -p /root/.cache/torch/hub/checkpoints\n!cp /kaggle/input/aliked/pytorch/aliked-n16/1/aliked-n16.pth /root/.cache/torch/hub/checkpoints/\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-05-11T06:32:35.731399Z","iopub.execute_input":"2025-05-11T06:32:35.731591Z","iopub.status.idle":"2025-05-11T06:32:37.675746Z","shell.execute_reply.started":"2025-05-11T06:32:35.731571Z","shell.execute_reply":"2025-05-11T06:32:37.674798Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# GLOMAP\n# you should first add glomap to your notebook\n# to do so, go to Input -> Add Input -> search for \"https://www.kaggle.com/datasets/bulkobubulko/glomap\"\n# it should appear in Datasets. \n\n# add required libs for glomap\nimport os\nos.environ['LD_LIBRARY_PATH'] = os.path.join('/kaggle/input/glomap/', 'glomap_package', 'lib') + ':' + os.environ.get('LD_LIBRARY_PATH', '')\n\n!cp /kaggle/input/glomap/glomap_package/bin/glomap /kaggle/working/\n!chmod +x /kaggle/working/glomap","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-11T06:32:40.560457Z","iopub.execute_input":"2025-05-11T06:32:40.560772Z","iopub.status.idle":"2025-05-11T06:32:40.928562Z","shell.execute_reply.started":"2025-05-11T06:32:40.560740Z","shell.execute_reply":"2025-05-11T06:32:40.927494Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# glomap works!!\n!cd /kaggle/working && ./glomap -h","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# FastMap\n# !pip install --no-index /kaggle/input/fastmap-dependencies/kaggle/working/fastmap-wheels/* --no-deps\n!find /kaggle/input/fastmap-dependencies/kaggle/working/fastmap-wheels/* -name \"*.whl\" | grep -v -E \"numpy|PyYAML\" | xargs pip install --no-deps","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-11T07:18:44.003720Z","iopub.execute_input":"2025-05-11T07:18:44.004084Z","iopub.status.idle":"2025-05-11T07:18:46.571535Z","shell.execute_reply.started":"2025-05-11T07:18:44.004056Z","shell.execute_reply":"2025-05-11T07:18:46.570630Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# and fastmap works toooo\n!python /kaggle/input/fastmap/run.py","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-11T07:23:21.642496Z","iopub.execute_input":"2025-05-11T07:23:21.642817Z","iopub.status.idle":"2025-05-11T07:23:24.014104Z","shell.execute_reply.started":"2025-05-11T07:23:21.642790Z","shell.execute_reply":"2025-05-11T07:23:24.013047Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# !rm -rf /kaggle/working/result/featureout/imc2023_heritage/fastmap_rec_aliked ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-11T10:35:56.914578Z","iopub.execute_input":"2025-05-11T10:35:56.914878Z","iopub.status.idle":"2025-05-11T10:35:57.080243Z","shell.execute_reply.started":"2025-05-11T10:35:56.914856Z","shell.execute_reply":"2025-05-11T10:35:57.079103Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# with default params fastmap does not converge \n\nfastmap_config = \"\"\"\ndistortion:\n  num_levels: 2\n  num_samples: 6\n  alpha_min: -0.3\n  alpha_max: 0.2\nfocal:\n  min_fov: 30.0\n  max_fov: 140.0\n  num_samples: 40\n  std: 0.03\nfundamental:\n  num_iters: 6\nhomography:\n  num_iters: 6\npose_decomposition:\n  error_thr: 0.03\nrotation:\n  max_inlier_thr: 24\n  min_inlier_thr: 8\n  min_inlier_increment_frac: 0.25\n  max_angle_thr: 15.0\n  min_angle_thr: 5.0\n  angle_step: 2.0\n  lr: 0.01\n  log_interval: 100\ntrack:\n  min_track_size: 2\nrelative_translation:\n  num_candidates: 128\n  epipolar_error_thr: 0.1\n  ray_angle_thr: 1.0\n  min_num_inliers: 3\n  min_degree: 1\nglobal_translation:\n  num_init: 2\n  log_interval: 50\nepipolar_adjustment:\n  num_irls_steps: 1\n  num_prune_steps: 1\n  max_thr: 0.05\n  min_thr: 0.02\n  lr: 0.005\n  lr_decay: 0.8\n  log_interval: 50\nsparse_reconstruction:\n  reproj_err_thr: 25.0\n  min_ray_angle: 0.5\n  batch_size: 1024\n\"\"\"\n \nconfig_path = \"/kaggle/working/config.yaml\"\nwith open(config_path, \"w\") as f:\n    f.write(fastmap_config)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-11T11:44:11.543828Z","iopub.execute_input":"2025-05-11T11:44:11.544221Z","iopub.status.idle":"2025-05-11T11:44:11.549473Z","shell.execute_reply.started":"2025-05-11T11:44:11.544186Z","shell.execute_reply":"2025-05-11T11:44:11.548617Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# to test fastmap\n# !python /kaggle/input/fastmap/run.py --headless --database /kaggle/working/result/featureout/imc2023_heritage/colmap.db --image_dir /kaggle/input/image-matching-challenge-2025/train/imc2023_heritage/ --output_dir /kaggle/working/result/featureout/imc2023_heritage/fastmap_rec_aliked --config /kaggle/working/config.yaml","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-11T11:44:24.056794Z","iopub.execute_input":"2025-05-11T11:44:24.057181Z","iopub.status.idle":"2025-05-11T11:44:24.060744Z","shell.execute_reply.started":"2025-05-11T11:44:24.057140Z","shell.execute_reply":"2025-05-11T11:44:24.059850Z"}},"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\nimport torch\nfrom lightglue import match_pair\nfrom lightglue import ALIKED, LightGlue\nfrom lightglue.utils import load_image, rbd\nfrom transformers import AutoImageProcessor, AutoModel\n\n# 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\n\nimport subprocess","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-11T06:32:55.956456Z","iopub.execute_input":"2025-05-11T06:32:55.956762Z","iopub.status.idle":"2025-05-11T06:33:16.483786Z","shell.execute_reply.started":"2025-05-11T06:32:55.956735Z","shell.execute_reply":"2025-05-11T06:33:16.483082Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Don't 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-05-11T06:33:16.484791Z","iopub.execute_input":"2025-05-11T06:33:16.485330Z","iopub.status.idle":"2025-05-11T06:33:16.543971Z","shell.execute_reply.started":"2025-05-11T06:33:16.485305Z","shell.execute_reply":"2025-05-11T06:33:16.543006Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_torch_image(fname, device=torch.device('cpu')):\n    img = K.io.load_image(fname, K.io.ImageLoadType.RGB32, device=device)[None, ...]\n    return img\n\n\n# Use efficientnet global descriptor to get matching shortlists.\ndef get_global_desc(fnames, device = torch.device('cpu')):\n    processor = AutoImageProcessor.from_pretrained('/kaggle/input/dinov2/pytorch/base/1')\n    model = AutoModel.from_pretrained('/kaggle/input/dinov2/pytorch/base/1')\n    model = model.eval()\n    model = model.to(device)\n    global_descs_dinov2 = []\n    for i, img_fname_full in tqdm(enumerate(fnames),total= len(fnames)):\n        key = os.path.splitext(os.path.basename(img_fname_full))[0]\n        timg = load_torch_image(img_fname_full)\n        with torch.inference_mode():\n            inputs = processor(images=timg, return_tensors=\"pt\", do_rescale=False).to(device)\n            outputs = model(**inputs)\n            dino_mac = F.normalize(outputs.last_hidden_state[:,1:].max(dim=1)[0], dim=1, p=2)\n        global_descs_dinov2.append(dino_mac.detach().cpu())\n    global_descs_dinov2 = torch.cat(global_descs_dinov2, dim=0)\n    return global_descs_dinov2\n\n\ndef get_img_pairs_exhaustive(img_fnames):\n    index_pairs = []\n    for i in range(len(img_fnames)):\n        for j in range(i+1, len(img_fnames)):\n            index_pairs.append((i,j))\n    return index_pairs\n\n\ndef get_image_pairs_shortlist(fnames,\n                              sim_th = 0.6, # should be strict\n                              min_pairs = 20,\n                              exhaustive_if_less = 20,\n                              device=torch.device('cpu')):\n    num_imgs = len(fnames)\n    if num_imgs <= exhaustive_if_less:\n        return get_img_pairs_exhaustive(fnames)\n    descs = get_global_desc(fnames, device=device)\n    dm = torch.cdist(descs, descs, p=2).detach().cpu().numpy()\n    # removing half\n    mask = dm <= sim_th\n    total = 0\n    matching_list = []\n    ar = np.arange(num_imgs)\n    already_there_set = []\n    for st_idx in range(num_imgs-1):\n        mask_idx = mask[st_idx]\n        to_match = ar[mask_idx]\n        if len(to_match) < min_pairs:\n            to_match = np.argsort(dm[st_idx])[:min_pairs]  \n        for idx in to_match:\n            if st_idx == idx:\n                continue\n            if dm[st_idx, idx] < 1000:\n                matching_list.append(tuple(sorted((st_idx, idx.item()))))\n                total+=1\n    matching_list = sorted(list(set(matching_list)))\n    return matching_list\n\ndef detect_aliked(img_fnames,\n                  feature_dir = '.featureout',\n                  num_features = 4096,\n                  resize_to = 1024,\n                  device=torch.device('cpu')):\n    dtype = torch.float32 # ALIKED has issues with float16\n    extractor = ALIKED(max_num_keypoints=num_features, detection_threshold=0.01, resize=resize_to).eval().to(device, dtype)\n    if not os.path.isdir(feature_dir):\n        os.makedirs(feature_dir)\n    with h5py.File(f'{feature_dir}/keypoints.h5', mode='w') as f_kp, \\\n         h5py.File(f'{feature_dir}/descriptors.h5', mode='w') as f_desc:\n        for img_path in tqdm(img_fnames):\n            img_fname = img_path.split('/')[-1]\n            key = img_fname\n            with torch.inference_mode():\n                image0 = load_torch_image(img_path, device=device).to(dtype)\n                feats0 = extractor.extract(image0)  # auto-resize the image, disable with resize=None\n                kpts = feats0['keypoints'].reshape(-1, 2).detach().cpu().numpy()\n                descs = feats0['descriptors'].reshape(len(kpts), -1).detach().cpu().numpy()\n                f_kp[key] = kpts\n                f_desc[key] = descs\n    return\n\ndef match_with_lightglue(img_fnames,\n                   index_pairs,\n                   feature_dir = '.featureout',\n                   device=torch.device('cpu'),\n                   min_matches=15,verbose=True):\n    lg_matcher = KF.LightGlueMatcher(\"aliked\", {\"width_confidence\": -1,\n                                                \"depth_confidence\": -1,\n                                                 \"mp\": True if 'cuda' in str(device) else False}).eval().to(device)\n    with h5py.File(f'{feature_dir}/keypoints.h5', mode='r') as f_kp, \\\n        h5py.File(f'{feature_dir}/descriptors.h5', mode='r') as f_desc, \\\n        h5py.File(f'{feature_dir}/matches.h5', mode='w') as f_match:\n        for pair_idx in tqdm(index_pairs):\n            idx1, idx2 = pair_idx\n            fname1, fname2 = img_fnames[idx1], img_fnames[idx2]\n            key1, key2 = fname1.split('/')[-1], fname2.split('/')[-1]\n            kp1 = torch.from_numpy(f_kp[key1][...]).to(device)\n            kp2 = torch.from_numpy(f_kp[key2][...]).to(device)\n            desc1 = torch.from_numpy(f_desc[key1][...]).to(device)\n            desc2 = torch.from_numpy(f_desc[key2][...]).to(device)\n            with torch.inference_mode():\n                dists, idxs = lg_matcher(desc1,\n                                         desc2,\n                                         KF.laf_from_center_scale_ori(kp1[None]),\n                                         KF.laf_from_center_scale_ori(kp2[None]))\n            if len(idxs)  == 0:\n                continue\n            n_matches = len(idxs)\n            if verbose:\n                print (f'{key1}-{key2}: {n_matches} matches')\n            group  = f_match.require_group(key1)\n            if n_matches >= min_matches:\n                 group.create_dataset(key2, data=idxs.detach().cpu().numpy().reshape(-1, 2))\n    return\n\ndef import_into_colmap(img_dir, feature_dir ='.featureout', database_path = 'colmap.db'):\n    db = COLMAPDatabase.connect(database_path)\n    db.create_tables()\n    single_camera = False\n    fname_to_id = add_keypoints(db, feature_dir, img_dir, '', 'simple-pinhole', single_camera)\n    add_matches(\n        db,\n        feature_dir,\n        fname_to_id,\n    )\n    db.commit()\n    return\n\ndef run_glomap(subcommand: str, flags: dict):\n    cmd = [\"./glomap\", subcommand]\n    for flag_name, value in flags.items():\n        cmd += [f\"--{flag_name}\", str(value)]\n    print(\"Running:\", \" \".join(cmd))\n    subprocess.run(cmd, check=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-11T06:33:16.545485Z","iopub.execute_input":"2025-05-11T06:33:16.545731Z","iopub.status.idle":"2025-05-11T06:33:16.563623Z","shell.execute_reply.started":"2025-05-11T06:33:16.545710Z","shell.execute_reply":"2025-05-11T06:33:16.562786Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Collect 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-05-11T06:33:18.421609Z","iopub.execute_input":"2025-05-11T06:33:18.421902Z","iopub.status.idle":"2025-05-11T06:33:18.568598Z","shell.execute_reply.started":"2025-05-11T06:33:18.421878Z","shell.execute_reply":"2025-05-11T06:33:18.567948Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# !rm -rf result\ngc.collect()\n\nmax_images = None  # For debugging only. Set to None to disable.\ndatasets_to_process = None  # Not the best convention, but None means all datasets.\nis_colmap = False # choose either this\nis_glomap = False # or this \nis_fastmap = True \n\nif is_train:\n    max_images = 50\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', # cyprus\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 = os.path.join(data_dir, 'train' if is_train else 'test', dataset)\n    images = [os.path.join(images_dir, p.filename) for p in predictions]\n    if max_images is not None:\n        images = images[:max_images]\n\n    print(f'\\nProcessing dataset \"{dataset}\": {len(images)} images')\n\n    filename_to_index = {p.filename: idx for idx, p in enumerate(predictions)}\n\n    feature_dir = os.path.join(workdir, 'featureout', dataset)\n    os.makedirs(feature_dir, exist_ok=True)\n\n    # Wrap algos in try-except blocks so we can populate a submission even if one scene crashes.\n    try:\n        t = time()\n        index_pairs = get_image_pairs_shortlist(\n            images,\n            sim_th = 0.3, # should be strict\n            min_pairs = 20, # we select at least min_pairs PER IMAGE with biggest similarity\n            exhaustive_if_less = 20,\n            device=device\n        )\n        timings['shortlisting'].append(time() - t)\n        print (f'Shortlisting. Number of pairs to match: {len(index_pairs)}. Done in {time() - t:.4f} sec')\n        gc.collect()\n    \n        t = time()\n\n        detect_aliked(images, feature_dir, 4096, device=device)\n        gc.collect()\n        timings['feature_detection'].append(time() - t)\n        print(f'Features detected in {time() - t:.4f} sec')\n        \n        t = time()\n        match_with_lightglue(images, index_pairs, feature_dir=feature_dir, device=device, verbose=False)\n        timings['feature_matching'].append(time() - t)\n        print(f'Features matched in {time() - t:.4f} sec')\n\n        database_path = os.path.join(feature_dir, 'colmap.db')\n        if os.path.isfile(database_path):\n            os.remove(database_path)\n        gc.collect()\n        sleep(1)\n        import_into_colmap(images_dir, feature_dir=feature_dir, database_path=database_path)\n                \n        t = time()\n        pycolmap.match_exhaustive(database_path)\n        timings['RANSAC'].append(time() - t)\n        print(f'Ran RANSAC in {time() - t:.4f} sec')\n        \n        t = time()\n\n        if is_colmap:\n            output_path = f'{feature_dir}/colmap_rec_aliked'\n            os.makedirs(output_path, exist_ok=True)\n            \n            # By default colmap does not generate a reconstruction if less than 10 images are registered.\n            # Lower it to 3.\n            mapper_options = pycolmap.IncrementalPipelineOptions()\n            mapper_options.min_model_size = 3\n            mapper_options.max_num_models = 25\n            \n            maps = pycolmap.incremental_mapping(\n                database_path=database_path, \n                image_path=images_dir,\n                output_path=output_path,\n                options=mapper_options)\n\n        if is_glomap:\n            output_path = f'{feature_dir}/glomap_rec_aliked'\n            os.makedirs(output_path, exist_ok=True)\n            \n            run_glomap(\"mapper\", {\n            \"database_path\": database_path,\n            \"image_path\":    images_dir,\n            \"output_path\":   output_path,\n            \"TrackEstablishment.min_num_view_per_track\": 3, # min num of view per point, default 3\n            \"Thresholds.min_inlier_num\": 150, # default 30, lowering perfoms worse \n            # \"Thresholds.min_inlier_ratio\": 0.05,\n            # \"RelPoseEstimation.max_epipolar_error\": 4, # 4, 10; default 1\n            # \"Triangulation.complete_max_reproj_error\":10,\n            # \"Triangulation.merge_max_reproj_error\": 10,\n            \"GlobalPositioning.optimize_positions\": 1, # default 1\n            })\n            \n            maps = {}\n            model_idx = 0\n            while os.path.exists(f\"{output_path}/{model_idx}\"):\n                try:\n                    recon = pycolmap.Reconstruction(f\"{output_path}/{model_idx}\")\n                    maps[model_idx] = recon\n                    model_idx += 1\n                except:\n                    break\n\n        if is_fastmap:\n            output_path = f'{feature_dir}/fastmap_rec_aliked'\n            # os.makedirs(output_path, exist_ok=True) # fastmap creates this dir on its own\n            \n            fastmap_cmd = f\"python /kaggle/input/fastmap/run.py --headless --database {database_path} --image_dir {images_dir}/ --output_dir {output_path} --config /kaggle/working/config.yaml\"\n            # fastmap_cmd = f\"python /kaggle/input/fastmap/run.py --headless --database {database_path} --image_dir {images_dir}/ --output_dir {output_path}\"\n            \n            \n            print(\"Running FastMap\")\n            subprocess.run(fastmap_cmd, shell=True, check=True)\n            \n            maps = {}\n            model_idx = 0\n            while os.path.exists(f\"{output_path}/sparse/{model_idx}\"):\n                try:\n                    recon = pycolmap.Reconstruction(f\"{output_path}/sparse/{model_idx}\")\n                    maps[model_idx] = recon\n                    model_idx += 1\n                except:\n                    break\n\n        sleep(1)\n        timings['Reconstruction'].append(time() - t)\n        print(f'Reconstruction done in  {time() - t:.4f} sec')\n        print(maps)\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(images)} images with {len(maps)} clusters'\n        mapping_result_strs.append(mapping_result_str)\n        print(mapping_result_str)\n        gc.collect()\n    except Exception as e:\n        print(e)\n        # raise e\n        mapping_result_str = f'Dataset \"{dataset}\" -> Failed!'\n        mapping_result_strs.append(mapping_result_str)\n        print(mapping_result_str)\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-05-11T13:52:16.635435Z","iopub.execute_input":"2025-05-11T13:52:16.635787Z","iopub.status.idle":"2025-05-11T14:10:18.899457Z","shell.execute_reply.started":"2025-05-11T13:52:16.635762Z","shell.execute_reply":"2025-05-11T14:10:18.898499Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create a submission file.\n\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-05-11T14:12:57.825903Z","iopub.execute_input":"2025-05-11T14:12:57.826315Z","iopub.status.idle":"2025-05-11T14:12:58.006168Z","shell.execute_reply.started":"2025-05-11T14:12:57.826283Z","shell.execute_reply":"2025-05-11T14:12:58.004982Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Compute results if running on the training set.\n# Don't 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-05-11T14:12:59.366049Z","iopub.execute_input":"2025-05-11T14:12:59.366435Z","iopub.status.idle":"2025-05-11T14:13:31.680159Z","shell.execute_reply.started":"2025-05-11T14:12:59.366403Z","shell.execute_reply":"2025-05-11T14:13:31.679260Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}