{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":49349,"databundleVersionId":5447706,"sourceType":"competition"},{"sourceId":5706785,"sourceType":"datasetVersion","datasetId":3278289},{"sourceId":5887486,"sourceType":"datasetVersion","datasetId":3374783},{"sourceId":6003552,"sourceType":"datasetVersion","datasetId":3270798},{"sourceId":6004068,"sourceType":"datasetVersion","datasetId":3286300}],"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Copy Inputs","metadata":{}},{"cell_type":"code","source":"# copy retrieval weights\n!mkdir -p /root/.cache/torch/hub/netvlad\n!cp /kaggle/input/imc-23-weigths/Pitts30K_struct.mat /root/.cache/torch/hub/netvlad/VGG16-NetVLAD-Pitts30K.mat\n!cp -r /kaggle/input/imc-23-weigths/cosplace/gmberton_cosplace_main /root/.cache/torch/hub/gmberton_CosPlace_main\n!cp -r /kaggle/input/imc-23-weigths/cosplace/checkpoints /root/.cache/torch/hub/checkpoints\n\n# copy rotation prediction\n!mkdir -p /root/.cache/huggingface/\n!cp -r /kaggle/input/orientation-prediction-model/hub /root/.cache/huggingface/\n\n# copy run_pixsfm.py script to working directory and make it executable \n!cp /kaggle/input/imc-23-repo/IMC-2023/run_pixsfm.py /kaggle/working/run_pixsfm.py\n!chmod 777 run_pixsfm.py\n\n# copy hloc_reconstruction.py script to working directory and make it executable\n!cp /kaggle/input/imc-23-repo/IMC-2023/hloc_reconstruction.py /kaggle/working/hloc_reconstruction.py\n!chmod 777 hloc_reconstruction.py","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2023-06-14T13:23:56.712378Z","iopub.execute_input":"2023-06-14T13:23:56.712746Z","iopub.status.idle":"2023-06-14T13:24:17.049011Z","shell.execute_reply.started":"2023-06-14T13:23:56.712694Z","shell.execute_reply":"2023-06-14T13:24:17.047598Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Create venv for PixSfM","metadata":{}},{"cell_type":"code","source":"!python -m venv venv","metadata":{"execution":{"iopub.status.busy":"2023-06-14T13:24:17.051892Z","iopub.execute_input":"2023-06-14T13:24:17.052313Z","iopub.status.idle":"2023-06-14T13:24:22.619662Z","shell.execute_reply.started":"2023-06-14T13:24:17.052275Z","shell.execute_reply":"2023-06-14T13:24:22.618397Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!cp -r /kaggle/input/imc-23-repo/venv .","metadata":{"execution":{"iopub.status.busy":"2023-06-14T13:24:22.621507Z","iopub.execute_input":"2023-06-14T13:24:22.62196Z","iopub.status.idle":"2023-06-14T13:27:27.949466Z","shell.execute_reply.started":"2023-06-14T13:24:22.621917Z","shell.execute_reply":"2023-06-14T13:27:27.948228Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# copy pixsfm d2net\n!mkdir -p /kaggle/working/venv/lib/python3.10/site-packages/pixsfm/features/models/checkpoints\n!cp /kaggle/input/imc-23-weigths/s2dnet_weights.pth /kaggle/working/venv/lib/python3.10/site-packages/pixsfm/features/models/checkpoints/s2dnet_weights.pth","metadata":{"execution":{"iopub.status.busy":"2023-06-14T13:27:27.95278Z","iopub.execute_input":"2023-06-14T13:27:27.953161Z","iopub.status.idle":"2023-06-14T13:27:30.524934Z","shell.execute_reply.started":"2023-06-14T13:27:27.953124Z","shell.execute_reply":"2023-06-14T13:27:30.523701Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!ls venv/bin","metadata":{"execution":{"iopub.status.busy":"2023-06-14T13:27:30.527623Z","iopub.execute_input":"2023-06-14T13:27:30.528272Z","iopub.status.idle":"2023-06-14T13:27:31.494893Z","shell.execute_reply.started":"2023-06-14T13:27:30.528231Z","shell.execute_reply":"2023-06-14T13:27:31.493797Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Install Dependencies","metadata":{}},{"cell_type":"code","source":"# install hloc\n# %pip install -q /kaggle/input/imc-23-repo/IMC-2023/wheels/hloc-1.3-py3-none-any.whl --no-deps\n\n# install disk deps\n!pip install -q /kaggle/input/imc-23-repo/IMC-2023/wheels/torch_dimcheck-0.0.1-py3-none-any.whl\n!pip install -q /kaggle/input/imc-23-repo/IMC-2023/wheels/unets-0.2.0-py3-none-any.whl\n!pip install -q /kaggle/input/imc-23-repo/IMC-2023/wheels/torch_localize-0.1.0-py3-none-any.whl\n\n# install lightglue deps\n!pip install -q /kaggle/input/imc-23-repo/IMC-2023/wheels/einops-0.6.1-py3-none-any.whl\n\n# install rotation predition\n!pip install -q /kaggle/input/imc-23-repo/IMC-2023/wheels/dioad-1.0.0-py3-none-any.whl\n\n# install ALIKED deps\n!pip install -q /kaggle/input/imc-23-repo/IMC-2023/wheels/thop-0.1.1.post2209072238-py3-none-any.whl --no-deps","metadata":{"execution":{"iopub.status.busy":"2023-06-14T13:27:31.496418Z","iopub.execute_input":"2023-06-14T13:27:31.497132Z","iopub.status.idle":"2023-06-14T13:30:30.65596Z","shell.execute_reply.started":"2023-06-14T13:27:31.497094Z","shell.execute_reply":"2023-06-14T13:30:30.65479Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# tf version of original env\n# !pip install -q /kaggle/input/imc-23-repo/IMC-2023/wheels/protobuf-3.19.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl --no-deps\n# !pip install -q /kaggle/input/imc-23-repo/IMC-2023/wheels/google_auth-2.19.1-py2.py3-none-any.whl --no-deps\n# !pip install -q /kaggle/input/imc-23-repo/IMC-2023/wheels/google_auth_oauthlib-0.4.6-py2.py3-none-any.whl --no-deps\n# !pip install -q /kaggle/input/imc-23-repo/IMC-2023/wheels/tensorboard_data_server-0.6.1-py3-none-manylinux2010_x86_64.whl --no-deps\n# !pip install -q /kaggle/input/imc-23-repo/IMC-2023/wheels/tensorboard_plugin_wit-1.8.1-py3-none-any.whl --no-deps\n# !pip install -q /kaggle/input/imc-23-repo/IMC-2023/wheels/tensorboard-2.11.2-py3-none-any.whl --no-deps\n# !pip install -q /kaggle/input/imc-23-repo/IMC-2023/wheels/tensorflow_estimator-2.11.0-py2.py3-none-any.whl --no-deps\n# !pip install -q /kaggle/input/imc-23-repo/IMC-2023/wheels/tensorflow_io_gcs_filesystem-0.32.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl --no-deps\n# !pip install -q /kaggle/input/imc-23-repo/IMC-2023/wheels/keras-2.11.0-py2.py3-none-any.whl --no-deps\n# !pip install -q /kaggle/input/imc-23-repo/IMC-2023/wheels/tensorflow-2.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl","metadata":{"execution":{"iopub.status.busy":"2023-06-14T13:30:30.658605Z","iopub.execute_input":"2023-06-14T13:30:30.659343Z","iopub.status.idle":"2023-06-14T13:30:30.665608Z","shell.execute_reply.started":"2023-06-14T13:30:30.659301Z","shell.execute_reply":"2023-06-14T13:30:30.664451Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Run Pipeline","metadata":{}},{"cell_type":"code","source":"!pwd","metadata":{"execution":{"iopub.status.busy":"2023-06-14T13:30:30.667638Z","iopub.execute_input":"2023-06-14T13:30:30.668315Z","iopub.status.idle":"2023-06-14T13:30:31.603594Z","shell.execute_reply.started":"2023-06-14T13:30:30.66828Z","shell.execute_reply":"2023-06-14T13:30:31.602407Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!ls","metadata":{"execution":{"iopub.status.busy":"2023-06-14T13:30:31.605642Z","iopub.execute_input":"2023-06-14T13:30:31.606026Z","iopub.status.idle":"2023-06-14T13:30:32.535067Z","shell.execute_reply.started":"2023-06-14T13:30:31.605988Z","shell.execute_reply":"2023-06-14T13:30:32.533969Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Adjust pixsfm config\nimport yaml\n\nconf_path = \"/kaggle/input/imc-23-repo/IMC-2023/ext_deps/pixel-perfect-sfm/pixsfm/configs/low_memory.yaml\"\n\nwith open(conf_path, 'r') as f:\n    conf = yaml.load(f, Loader=yaml.FullLoader)\n\nconf[\"mapping\"][\"BA\"][\"optimizer\"][\"refine_focal_length\"] = True\nconf[\"mapping\"][\"BA\"][\"optimizer\"][\"refine_extra_params\"] = True\nconf[\"mapping\"][\"BA\"][\"optimizer\"][\"refine_extrinsics\"] = True\n\nwith open(\"pixsfm.yaml\", 'w') as f:\n    yaml.dump(conf, f)","metadata":{"execution":{"iopub.status.busy":"2023-06-14T13:30:32.539956Z","iopub.execute_input":"2023-06-14T13:30:32.540269Z","iopub.status.idle":"2023-06-14T13:30:32.575283Z","shell.execute_reply.started":"2023-06-14T13:30:32.54024Z","shell.execute_reply":"2023-06-14T13:30:32.574467Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import sys\n\nsys.path.append(\"/kaggle/input/imc-23-repo/IMC-2023\")\nsys.path.append(\"/kaggle/input/imc-23-repo/IMC-2023/ext_deps/Hierarchical-Localization\")\nsys.path.append(\"/kaggle/input/imc-23-repo/IMC-2023/ext_deps/dioad\")\n\nimport os\nimport argparse\n\nfrom main import main\n\nargs = {\n    \"data\": \"/kaggle/input/image-matching-challenge-2023\",\n    \"configs\": [\"ALIKED2K\", \"DISK\", \"SIFT\"],\n    \"retrieval\": \"netvlad\",\n    \"n_retrieval\": 30,\n    \"mode\": \"train\",\n    \"output\": \"/kaggle/temp\",\n    \"pixsfm\": True,\n    \"pixsfm_max_imgs\": 9999,\n    \"pixsfm_low_mem_threshold\": 9999,\n    \"pixsfm_config\": \"/kaggle/working/pixsfm.yaml\",\n    \"pixsfm_script_path\": \"/kaggle/input/imc-23-repo/IMC-2023/run_pixsfm.py\",\n    \"rotation_matching\": False,\n    \"rotation_wrapper\": False,\n    \"resize\": None,\n    \"shared_camera\": True,\n    \"overwrite\": True,\n    \"kaggle\": True,\n    \"cropping\": False,\n    \"max_rel_crop_size\": 1,\n    \"min_rel_crop_size\": 0,\n    \"localize_unregistered\": True,\n    \"skip_scenes\": None,\n}\n\nargs = argparse.Namespace(**args)\nos.makedirs(args.output, exist_ok=True)\n\nmain(args)","metadata":{"execution":{"iopub.status.busy":"2023-06-14T13:51:09.641831Z","iopub.execute_input":"2023-06-14T13:51:09.642187Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!rm -rf venv","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!ls -l","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!ls ../temp","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Making kornia local features loading w/o internet\nclass AffNetHardNet(KF.LocalFeature):\n    \"\"\"Convenience module, which implements KeyNet detector + AffNet + HardNet descriptor.\n\n    .. image:: _static/img/keynet_affnet.jpg\n    \"\"\"\n\n    def __init__(\n        self,\n        num_features: int = 5000,\n        upright: bool = False,\n        device = torch.device('cpu'),\n        scale_laf: float = 1.0,\n        detector_type='GFTT',\n    ):\n        config = {\n            # Extraction Parameters\n            \"nms_size\": 15,\n            \"pyramid_levels\": 4,\n            \"up_levels\": 1,\n            \"scale_factor_levels\": math.sqrt(2),\n            \"s_mult\": 22.0,\n        }\n        ori_module = KF.PassLAF() if upright else KF.LAFOrienter(angle_detector=KF.OriNet(False)).eval()\n        if not upright:\n            weights = torch.load('/kaggle/input/kornia-local-feature-weights/OriNet.pth')['state_dict']\n            ori_module.angle_detector.load_state_dict(weights)\n        #detector = KF.KeyNetDetector(\n        #    False, num_features=num_features, ori_module=ori_module, aff_module=KF.LAFAffNetShapeEstimator(False).eval()\n        #).to(device)\n        if detector_type=='GFTT':\n            detector = KF.MultiResolutionDetector(\n                    KF.CornerGFTT(),\n                    num_features=num_features,\n                    config=config,\n                    ori_module=ori_module,\n                    aff_module=KF.LAFAffNetShapeEstimator(False).eval(),\n                ).to(device)\n        elif detector_type=='DoG':\n            detector = KF.MultiResolutionDetector(\n                    KF.BlobDoGSingle(),\n                    num_features=num_features,\n                    config=config,\n                    ori_module=ori_module,\n                    aff_module=KF.LAFAffNetShapeEstimator(False).eval(),\n                ).to(device)\n        else:\n            detector = KF.MultiResolutionDetector(\n                    KF.CornerHarris(0.04),\n                    num_features=num_features,\n                    config=config,\n                    ori_module=ori_module,\n                    aff_module=KF.LAFAffNetShapeEstimator(False).eval(),\n                ).to(device)\n        #kn_weights = torch.load('/kaggle/input/kornia-local-feature-weights/keynet_pytorch.pth')['state_dict']\n        #detector.model.load_state_dict(kn_weights)\n        affnet_weights = torch.load('/kaggle/input/kornia-local-feature-weights/AffNet.pth')['state_dict']\n        detector.aff.load_state_dict(affnet_weights)\n\n        hardnet = KF.HardNet(False).eval()\n        hn_weights = torch.load('/kaggle/input/kornia-local-feature-weights/HardNetLib.pth')['state_dict']\n        hardnet.load_state_dict(hn_weights)\n        descriptor = KF.LAFDescriptor(hardnet, patch_size=32, grayscale_descriptor=True).to(device)\n        super().__init__(detector, descriptor, scale_laf)\n\n# Making kornia local features loading w/o internet\nclass KeyNetAffNetHardNet(KF.LocalFeature):\n    \"\"\"Convenience module, which implements KeyNet detector + AffNet + HardNet descriptor.\n\n    .. image:: _static/img/keynet_affnet.jpg\n    \"\"\"\n\n    def __init__(\n        self,\n        num_features: int = 5000,\n        upright: bool = False,\n        device = torch.device('cpu'),\n        scale_laf: float = 1.0,\n\n    ):\n        ori_module = KF.PassLAF() if upright else KF.LAFOrienter(angle_detector=KF.OriNet(False)).eval()\n        if not upright:\n            weights = torch.load('/kaggle/input/kornia-local-feature-weights/OriNet.pth')['state_dict']\n            ori_module.angle_detector.load_state_dict(weights)\n        detector = KF.KeyNetDetector(\n            False, num_features=num_features, ori_module=ori_module, aff_module=KF.LAFAffNetShapeEstimator(False).eval()\n        ).to(device)\n        kn_weights = torch.load('/kaggle/input/kornia-local-feature-weights/keynet_pytorch.pth')['state_dict']\n        detector.model.load_state_dict(kn_weights)\n        affnet_weights = torch.load('/kaggle/input/kornia-local-feature-weights/AffNet.pth')['state_dict']\n        detector.aff.load_state_dict(affnet_weights)\n\n        hardnet = KF.HardNet(False).eval()\n        hn_weights = torch.load('/kaggle/input/kornia-local-feature-weights/HardNetLib.pth')['state_dict']\n        hardnet.load_state_dict(hn_weights)\n        descriptor = KF.LAFDescriptor(hardnet, patch_size=32, grayscale_descriptor=True).to(device)\n        super().__init__(detector, descriptor, scale_laf)\n\n\ndef detect_features(img_fnames,\n                    num_feats = 8000,\n                    upright = False,\n                    device=torch.device('cpu'),\n                    feature_dir = '.featureout',\n                    resize_small_edge_to = 1200,\n                local_feature='Keynet'):\n\n    if local_feature == 'Keynet':\n        feature = KeyNetAffNetHardNet(num_feats, upright, device).to(device).eval()\n    else:\n        feature = AffNetHardNet(num_feats, upright, device, detector_type=local_feature).to(device).eval()\n    print('local feature:', local_feature)\n    if not os.path.isdir(feature_dir):\n        os.makedirs(feature_dir)\n    with h5py.File(f'{feature_dir}/lafs.h5', mode='w') as f_laf, \\\n        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 progress_bar(img_fnames):\n            img_fname = img_path.split('/')[-1]\n            key = img_fname\n            with torch.inference_mode():\n                timg = load_torch_image(img_path, device=device)\n                H, W = timg.shape[2:]\n                if resize_small_edge_to is None:\n                    timg_resized = timg\n                else:\n                    timg_resized = K.geometry.resize(timg, resize_small_edge_to, antialias=True)\n                    print(f'Resized {timg.shape} to {timg_resized.shape} (resize_small_edge_to={resize_small_edge_to})')\n                h, w = timg_resized.shape[2:]\n\n                lafs, resps, descs = feature(K.color.rgb_to_grayscale(timg_resized))\n                lafs[:,:,0,:] *= float(W) / float(w)\n                lafs[:,:,1,:] *= float(H) / float(h)\n                desc_dim = descs.shape[-1]\n                kpts = KF.get_laf_center(lafs).reshape(-1, 2).detach().cpu().numpy()\n                descs = descs.reshape(-1, desc_dim).detach().cpu().numpy()\n                f_laf[key] = lafs.detach().cpu().numpy()\n                f_kp[key] = kpts\n                f_desc[key] = descs\n    return\n\ndef match_features(img_fnames,\n                index_pairs,\n                file_keypoints,\n                feature_dir = '.featureout',\n                device=torch.device('cpu'),\n                min_matches=15, \n                force_mutual = True,\n                matching_alg='adalam'\n                ):\n    assert matching_alg in ['smnn', 'adalam']\n    with h5py.File(f'{feature_dir}/lafs.h5', mode='r') as f_laf, \\\n        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(file_keypoints, mode='w') as f_match:\n\n        for pair_idx in progress_bar(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                    lafs1 = torch.from_numpy(f_laf[key1][...]).to(device)\n                    lafs2 = torch.from_numpy(f_laf[key2][...]).to(device)\n                    desc1 = torch.from_numpy(f_desc[key1][...]).to(device)\n                    desc2 = torch.from_numpy(f_desc[key2][...]).to(device)\n                    kp1 = torch.from_numpy(f_kp[key1][...]).to(device)\n                    kp2 = torch.from_numpy(f_kp[key2][...]).to(device)\n                    if matching_alg == 'adalam':\n                        img1, img2 = cv2.imread(fname1), cv2.imread(fname2)\n                        hw1, hw2 = img1.shape[:2], img2.shape[:2]\n                        adalam_config = KF.adalam.get_adalam_default_config()\n                        #adalam_config['orientation_difference_threshold'] = None\n                        #adalam_config['scale_rate_threshold'] = None\n                        adalam_config['force_seed_mnn']= False\n                        adalam_config['search_expansion'] = 16\n                        adalam_config['ransac_iters'] = 128\n                        adalam_config['device'] = device\n                        dists, idxs = KF.match_adalam(desc1, desc2,\n                                                    lafs1, lafs2, # Adalam takes into account also geometric information\n                                                    hw1=hw1, hw2=hw2,\n                                                    config=adalam_config) # Adalam also benefits from knowing image size\n                    else:\n                        dists, idxs = KF.match_smnn(desc1, desc2, 0.98)\n                    if len(idxs)  == 0:\n                        continue\n                    # Force mutual nearest neighbors\n                    if force_mutual:\n                        first_indices = get_unique_idxs(idxs[:,1])\n                        idxs = idxs[first_indices]\n                        dists = dists[first_indices]\n                    n_matches = len(idxs)\n                    kp1 = kp1[idxs[:,0], :].cpu().numpy().reshape(-1, 2).astype(np.float32)\n                    kp2 = kp2[idxs[:,1], :].cpu().numpy().reshape(-1, 2).astype(np.float32)\n                    if False:\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=np.concatenate([kp1, kp2], axis=1))\n    return\n\n\ndef get_unique_idxs(A, dim=0):\n    # https://stackoverflow.com/questions/72001505/how-to-get-unique-elements-and-their-firstly-appeared-indices-of-a-pytorch-tenso\n    unique, idx, counts = torch.unique(A, dim=dim, sorted=True, return_inverse=True, return_counts=True)\n    _, ind_sorted = torch.sort(idx, stable=True)\n    cum_sum = counts.cumsum(0)\n    cum_sum = torch.cat((torch.tensor([0],device=cum_sum.device), cum_sum[:-1]))\n    first_indices = ind_sorted[cum_sum]\n    return first_indices\n\ndef get_keypoint_from_h5(fp, key1, key2):\n    rc = -1\n    try:\n        kpts = np.array(fp[key1][key2])\n        rc = 0\n        return (rc, kpts)\n    except:\n        return (rc, None)\n\ndef get_keypoint_from_multi_h5(fps, key1, key2):\n    list_mkpts = []\n    for fp in fps:\n        rc, mkpts = get_keypoint_from_h5(fp, key1, key2)\n        if rc == 0:\n            list_mkpts.append(mkpts)\n    if len(list_mkpts) > 0:\n        list_mkpts = np.concatenate(list_mkpts, axis=0)\n    else:\n        list_mkpts = None\n    return list_mkpts\n\ndef matches_merger(\n    img_fnames,\n    index_pairs,\n    files_keypoints,\n    save_file,\n    feature_dir = 'featureout',\n    filter_FundamentalMatrix = False,\n    filter_iterations = 10,\n    filter_threshold = 8,\n):\n    # open h5 files\n    fps = [ h5py.File(file, mode=\"r\") for file in files_keypoints ]\n\n    with h5py.File(save_file, mode='w') as f_match:\n        counter = 0\n        for pair_idx in progress_bar(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\n            # extract keypoints\n            mkpts = get_keypoint_from_multi_h5(fps, key1, key2)\n            if mkpts is None:\n                print(f\"skipped key1={key1}, key2={key2}\")\n                continue\n\n            ori_size = mkpts.shape[0]\n            if mkpts.shape[0] < CONFIG.MERGE_PARAMS[\"min_matches\"]:\n                continue\n\n            if filter_FundamentalMatrix:\n                store_inliers = { idx:0 for idx in range(mkpts.shape[0]) }\n                idxs = np.array(range(mkpts.shape[0]))\n                for iter in range(filter_iterations):\n                    try:\n                        Fm, inliers = cv2.findFundamentalMat(\n                            mkpts[:,:2], mkpts[:,2:4], cv2.USAC_MAGSAC, 0.15, 0.9999, 20000)\n                        if Fm is not None:\n                            inliers = inliers > 0\n                            inlier_idxs = idxs[inliers[:, 0]]\n                            #print(inliers.shape, inlier_idxs[:5])\n                            for idx in inlier_idxs:\n                                store_inliers[idx] += 1\n                    except:\n                        print(f\"Failed to cv2.findFundamentalMat. mkpts.shape={mkpts.shape}\")\n                inliers = np.array([ count for (idx, count) in store_inliers.items() ]) >= filter_threshold\n                mkpts = mkpts[inliers]\n                if mkpts.shape[0] < 15:\n                    print(f\"skipped key1={key1}, key2={key2}: mkpts.shape={mkpts.shape} after filtered.\")\n                    continue\n                #print(f\"filter_FundamentalMatrix: {len(store_inliers)} matches --> {mkpts.shape[0]} matches\")\n\n\n            print (f'{key1}-{key2}: {ori_size} --> {mkpts.shape[0]} matches')            \n            # regist tmp file\n            group  = f_match.require_group(key1)\n            group.create_dataset(key2, data=mkpts)\n            counter += 1\n    print( f\"Ensembled pairs : {counter} pairs\" )\n    for fp in fps:\n        fp.close()","metadata":{},"execution_count":null,"outputs":[]}]}