{"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"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install mediapy","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2023-07-16T16:23:09.638986Z","iopub.execute_input":"2023-07-16T16:23:09.639440Z","iopub.status.idle":"2023-07-16T16:23:24.248466Z","shell.execute_reply.started":"2023-07-16T16:23:09.639401Z","shell.execute_reply":"2023-07-16T16:23:24.246796Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Get some utility functions from https://github.com/cvg/Hierarchical-Localization/\n\n%cd /kaggle/working/\n!rm -rf /kaggle/working/Hierarchical-Localization\n!git clone --quiet --recursive https://github.com/cvg/Hierarchical-Localization/\n%cd /kaggle/working/Hierarchical-Localization\n!pip install -e .\n\nfrom hloc import extract_features, match_features, reconstruction, visualization, pairs_from_exhaustive\nfrom hloc.visualization import plot_images, read_image\nfrom hloc.utils import viz_3d\n\n%cd /kaggle/working/","metadata":{"execution":{"iopub.status.busy":"2023-07-16T16:23:24.251391Z","iopub.execute_input":"2023-07-16T16:23:24.252210Z","iopub.status.idle":"2023-07-16T16:24:00.340521Z","shell.execute_reply.started":"2023-07-16T16:23:24.252159Z","shell.execute_reply":"2023-07-16T16:24:00.338002Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import os\nimport pycolmap\nimport numpy as np\nimport mediapy as media\nimport cv2\nfrom glob import glob\nfrom pathlib import Path\nfrom time import time","metadata":{"execution":{"iopub.status.busy":"2023-07-16T16:24:00.342193Z","iopub.execute_input":"2023-07-16T16:24:00.343215Z","iopub.status.idle":"2023-07-16T16:24:00.383184Z","shell.execute_reply.started":"2023-07-16T16:24:00.343177Z","shell.execute_reply":"2023-07-16T16:24:00.381527Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"dataset = 'urban'\nscene = 'kyiv-puppet-theater'\nsrc = f'/kaggle/input/image-matching-challenge-2023/train/{dataset}/{scene}'\n\nimages = [cv2.cvtColor(cv2.imread(im), cv2.COLOR_BGR2RGB) for im in glob(f'{src}/images/*')]","metadata":{"execution":{"iopub.status.busy":"2023-07-16T16:24:00.387322Z","iopub.execute_input":"2023-07-16T16:24:00.387799Z","iopub.status.idle":"2023-07-16T16:24:01.841539Z","shell.execute_reply.started":"2023-07-16T16:24:00.387744Z","shell.execute_reply":"2023-07-16T16:24:01.839675Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"media.show_images(images, height=300, columns=4)","metadata":{"execution":{"iopub.status.busy":"2023-07-16T16:24:01.844207Z","iopub.execute_input":"2023-07-16T16:24:01.845274Z","iopub.status.idle":"2023-07-16T16:24:03.775661Z","shell.execute_reply.started":"2023-07-16T16:24:01.845226Z","shell.execute_reply":"2023-07-16T16:24:03.774197Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Visualizing a Colmap reconstruction","metadata":{}},{"cell_type":"code","source":"# Visualize a ground-truth 3D reconstruction (points and cameras). Note that some of these might be very large!\n# You can also visualize them outside a notebook with the colmap gui: https://colmap.github.io/\n\nrec_gt = pycolmap.Reconstruction(f'{src}/sfm')\n\nfig = viz_3d.init_figure()\nviz_3d.plot_cameras(fig, rec_gt, color='rgba(50,255,50, 0.5)', name=\"Ground Truth\", size=10)\nviz_3d.plot_reconstruction(fig, rec_gt, cameras = False, color='rgba(255,50,255, 0.5)', name=\"Ground Truth\", cs=5)\nfig.show()","metadata":{"execution":{"iopub.status.busy":"2023-07-16T16:24:03.777180Z","iopub.execute_input":"2023-07-16T16:24:03.777761Z","iopub.status.idle":"2023-07-16T16:24:18.111177Z","shell.execute_reply.started":"2023-07-16T16:24:03.777697Z","shell.execute_reply":"2023-07-16T16:24:18.109475Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Creating a reconstruction with SIFT and Colmap","metadata":{}},{"cell_type":"code","source":"tgt = f'/kaggle/working/{dataset}-{scene}'\n\n# Generate a simple reconstruction with SIFT (https://en.wikipedia.org/wiki/Scale-invariant_feature_transform).\nif not os.path.isdir(tgt):\n    os.makedirs(f'{tgt}/bundle')\n    os.system(f'cp -r {src}/images {tgt}/images')\n\n    database_path = f'{tgt}/database.db'\n\n    sift_opt = pycolmap.SiftExtractionOptions()\n    sift_opt.max_image_size = 1500  # Extract features at low resolution could significantly reduce the overall accuracy\n    sift_opt.max_num_features = 8192  # Generally more features is better, even if behond a certain number it doesn't help incresing accuracy\n    sift_opt.upright = True  # rotation invariance\n    device = 'cpu'\n\n    t = time()\n    pycolmap.extract_features(database_path, f'{tgt}/images', sift_options=sift_opt, verbose=True)\n    print(len(os.listdir(f'{tgt}/images')))\n    print('TIMINGS --- Feature extraction', time() - t)\n\n    t = time()\n    matching_opt = pycolmap.SiftMatchingOptions()\n    matching_opt.max_ratio = 0.85 # Ratio threshold significantly influence the performance of the feature extraction method. It varies depending on the local feature but also on the image type \n#     matching_opt.max_distance = 0.7\n    matching_opt.cross_check = True\n    matching_opt.max_error = 1.0 # The ransac error threshold could help to exclude less accurate tie points\n    pycolmap.match_exhaustive(database_path, sift_options=matching_opt, device=device, verbose=True)\n    print('TIMINGS --- Feature matching', time() - t)\n\n    t = time()\n    mapper_options = pycolmap.IncrementalMapperOptions()\n    mapper_options.extract_colors = False\n    mapper_options.min_model_size = 3\n\n    # Sometimes you want to impose the first image pair for initialize the incremental reconstruction\n    mapper_options.init_image_id1 = -1 \n    mapper_options.init_image_id2 = -1\n\n    # Choose which interior will be refined during BA\n    mapper_options.ba_refine_focal_length = True\n    mapper_options.ba_refine_principal_point = True\n    mapper_options.ba_refine_extra_params = True\n\n    maps = pycolmap.incremental_mapping(database_path=database_path, image_path=f'{tgt}/images', output_path=f'{tgt}/bundle', options=mapper_options)\n    print('TIMINGS --- Mapping', time() - t)","metadata":{"execution":{"iopub.status.busy":"2023-07-16T16:24:18.113900Z","iopub.execute_input":"2023-07-16T16:24:18.114963Z","iopub.status.idle":"2023-07-16T16:36:53.445422Z","shell.execute_reply.started":"2023-07-16T16:24:18.114904Z","shell.execute_reply":"2023-07-16T16:36:53.444182Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"maps","metadata":{"execution":{"iopub.status.busy":"2023-07-16T16:36:53.447372Z","iopub.execute_input":"2023-07-16T16:36:53.448103Z","iopub.status.idle":"2023-07-16T16:36:53.457795Z","shell.execute_reply.started":"2023-07-16T16:36:53.448055Z","shell.execute_reply":"2023-07-16T16:36:53.456349Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Inspect the largest map generated by colmap. Note that it might produce more than one reconstruction.\n# Don't expect great results! SIFT might work well on some scenes, but not others.\n\nbest_index = None\nbest_num_reg_images = 0\nfor idx in maps:\n    if maps[idx].num_reg_images() > best_num_reg_images:\n        best_index = idx\n        best_num_reg_images = maps[idx].num_reg_images()\n\nif best_num_reg_images > 0:\n    print(f'Looking at reconstruction #{best_index} with {best_num_reg_images} registered images')\n\n    fig = viz_3d.init_figure()\n    viz_3d.plot_reconstruction(fig, maps[best_index], color='rgba(50,255,50, 0.5)', name=\"Reconstruction\", cs=5, cameras=False)\n    viz_3d.plot_reconstruction(fig, maps[best_index], color='rgba(255,50,255, 0.5)', name=\"Reconstruction\", cs=5, points=False)\n    fig.show()\nelse:\n    print('No reconstruction. :(')","metadata":{"execution":{"iopub.status.busy":"2023-07-16T16:36:53.459855Z","iopub.execute_input":"2023-07-16T16:36:53.460815Z","iopub.status.idle":"2023-07-16T16:36:53.621662Z","shell.execute_reply.started":"2023-07-16T16:36:53.460771Z","shell.execute_reply":"2023-07-16T16:36:53.619822Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Writing the \"submission\"","metadata":{}},{"cell_type":"code","source":"# Generate a \"submission\" from colmap.\n# Note that to get a functional submission you'll have to process every scene.\n\ndef arr_to_str(a):\n    return ';'.join([str(x) for x in a.reshape(-1)])\n\n# Get a list of images on this dataset.\nall_images = []\nwith open('/kaggle/input/image-matching-challenge-2023/train/train_labels.csv', 'r') as f:\n    for i, l in enumerate(f):\n        if i > 0 and l:\n            _dataset, _scene, image_path, _, _ = l.strip().split(',')\n            if dataset == _dataset and scene == _scene:\n                all_images.append(image_path)\n\nif best_num_reg_images > 0:\n    rec = maps[best_index]\n    \n    with open('submission.csv', 'w') as f:\n        f.write('image_path,dataset,scene,rotation_matrix,translation_vector\\n')\n        registered_images = []\n        for imgidx, img in rec.images.items():\n            R = img.rotmat()\n            T = img.tvec\n            img_name  = img.name\n            f.write(f'{dataset}/{scene}/images/{img_name},{dataset},{scene},{arr_to_str(R)},{arr_to_str(T)}\\n')\n            registered_images.append(img_name)\n        \n        # Fill out unregistered images with zeros or the score function will fail.\n        for img_name in all_images:\n            if img_name not in registered_images:\n                R = np.zeros(9)\n                T = np.zeros(3)\n                f.write(f'{img_name},{dataset},{scene},{arr_to_str(R)},{arr_to_str(T)}\\n')\n\n    !cat submission.csv","metadata":{"execution":{"iopub.status.busy":"2023-07-16T16:36:53.628527Z","iopub.execute_input":"2023-07-16T16:36:53.629890Z","iopub.status.idle":"2023-07-16T16:36:54.775231Z","shell.execute_reply.started":"2023-07-16T16:36:53.629836Z","shell.execute_reply":"2023-07-16T16:36:54.773714Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Better features than SIFT?\n\nLet's try one of the IMC2020 top-solutions - the DISK local feature extractor.\n\nWe will have to write code to extract features, and then import them into the colmap database. \n\nYou can find examples here: [imc2023-kornia-starter-pack](https://github.com/ducha-aiki/imc2023-kornia-starter-pack.git).","metadata":{}},{"cell_type":"code","source":"%cd /kaggle/working/\n!rm -rf /kaggle/working/imc2023-kornia-starter-pack\n!git clone --quiet --recursive https://github.com/ducha-aiki/imc2023-kornia-starter-pack.git\n\nimport sys\nsys.path.append('/kaggle/working/imc2023-kornia-starter-pack')\n\nimport kornia as K\nimport kornia.feature as KF\nimport torch\nimport h5py\nfrom fastprogress import progress_bar\n","metadata":{"execution":{"iopub.status.busy":"2023-07-16T16:36:54.777830Z","iopub.execute_input":"2023-07-16T16:36:54.778350Z","iopub.status.idle":"2023-07-16T16:37:00.067863Z","shell.execute_reply.started":"2023-07-16T16:36:54.778295Z","shell.execute_reply":"2023-07-16T16:37:00.066321Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# We will need images in pytorch format\ndef load_torch_image(fname, device=torch.device('cpu')):\n    img = K.image_to_tensor(cv2.imread(fname), False).float() / 255.\n    img = K.color.bgr_to_rgb(img.to(device))\n    return img\n\n# Load DISK w/o internet connection through Kaggle Models voodoo: while not necessary yet, it will be helpful for offline submissions, which require turning off internet access.\ndef load_DISK(device=torch.device('cpu')):\n    disk = KF.DISK().to(device)\n    pretrained_dict = torch.load('/kaggle/input/disk/pytorch/depth-supervision/1/loftr_outdoor.ckpt', map_location=device)\n    disk.load_state_dict(pretrained_dict['extractor'])\n    disk.eval()\n    return disk","metadata":{"execution":{"iopub.status.busy":"2023-07-16T16:37:00.074951Z","iopub.execute_input":"2023-07-16T16:37:00.079252Z","iopub.status.idle":"2023-07-16T16:37:00.094620Z","shell.execute_reply.started":"2023-07-16T16:37:00.079201Z","shell.execute_reply":"2023-07-16T16:37:00.093015Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def detect_features(img_fnames,\n                    num_feats = 2048,\n                    device=torch.device('cpu'),\n                    feature_dir = '.featureout'):\n    disk = load_DISK(device)\n    if not os.path.isdir(feature_dir):\n        os.makedirs(feature_dir)\n    \n    # We will save features to h5 files, and then will import them into colmap\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 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                features = disk(timg, num_feats, pad_if_not_divisible=True)[0]\n                kpts, descs = features.keypoints, features.descriptors\n            \n            # Convert to numpy to store in h5py\n            kpts = kpts.reshape(-1, 2).detach().cpu().numpy()\n            descs = descs.reshape(-1, 128).detach().cpu().numpy()\n            f_kp[key] = kpts\n            f_desc[key] = descs\n    return\n\nimg_fnames =  [fname for fname in glob(f'{src}/images/*')]\nfeature_dir = 'disk_features'\ndevice=torch.device('cuda')\ndetect_features(img_fnames, 5000, device=device, feature_dir=feature_dir)","metadata":{"execution":{"iopub.status.busy":"2023-07-16T16:37:00.101019Z","iopub.execute_input":"2023-07-16T16:37:00.103937Z","iopub.status.idle":"2023-07-16T16:37:14.613358Z","shell.execute_reply.started":"2023-07-16T16:37:00.103891Z","shell.execute_reply":"2023-07-16T16:37:14.612328Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"%%capture\n# kornia_moons for feature visualization\n!pip install kornia_moons","metadata":{"execution":{"iopub.status.busy":"2023-07-16T16:37:14.615126Z","iopub.execute_input":"2023-07-16T16:37:14.615600Z","iopub.status.idle":"2023-07-16T16:37:27.872094Z","shell.execute_reply.started":"2023-07-16T16:37:14.615535Z","shell.execute_reply":"2023-07-16T16:37:27.870540Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Let's visualize our detections\nfrom kornia_moons.feature import visualize_LAF\n\nimage_index = 5\nwith h5py.File(f'{feature_dir}/keypoints.h5', mode='r') as f_kp:\n    img1 = load_torch_image(img_fnames[image_index])\n    key = img_fnames[image_index].split('/')[-1]\n    lafs = KF.laf_from_center_scale_ori(torch.from_numpy(f_kp[key][...]).reshape(1,-1, 2))\n    visualize_LAF(img1, lafs)\n","metadata":{"execution":{"iopub.status.busy":"2023-07-16T16:37:27.875657Z","iopub.execute_input":"2023-07-16T16:37:27.876747Z","iopub.status.idle":"2023-07-16T16:37:35.822336Z","shell.execute_reply.started":"2023-07-16T16:37:27.876695Z","shell.execute_reply":"2023-07-16T16:37:35.821233Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Let's visualize our detections\nfrom kornia_moons.feature import visualize_LAF\n\nimage_index = 8\nwith h5py.File(f'{feature_dir}/keypoints.h5', mode='r') as f_kp:\n    img1 = load_torch_image(img_fnames[image_index])\n    key = img_fnames[image_index].split('/')[-1]\n    lafs = KF.laf_from_center_scale_ori(torch.from_numpy(f_kp[key][...]).reshape(1,-1, 2))\n    visualize_LAF(img1, lafs)","metadata":{"execution":{"iopub.status.busy":"2023-07-16T16:37:35.824378Z","iopub.execute_input":"2023-07-16T16:37:35.825135Z","iopub.status.idle":"2023-07-16T16:37:43.372149Z","shell.execute_reply.started":"2023-07-16T16:37:35.825094Z","shell.execute_reply":"2023-07-16T16:37:43.370964Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Now we will match our features on GPU with kornia\n\ndef match_features(img_fnames,\n                   index_pairs,\n                   feature_dir = '.featureout',\n                   device=torch.device('cpu'),\n                   min_matches=15, verbose = True):\n    with 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 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                    desc1 = torch.from_numpy(f_desc[key1][...]).to(device)\n                    desc2 = torch.from_numpy(f_desc[key2][...]).to(device)\n                    # Matching with mutual nearest neighbor check and Lowe's threshold\n                    dists, idxs = KF.match_smnn(desc1, desc2, 0.98)\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\n# matching all to all\nindex_pairs = []\nfor i in range(len(img_fnames)):\n    for j in range(i+1, len(img_fnames)):\n        index_pairs.append((i,j))\n\nmatch_features(img_fnames, index_pairs, device=torch.device('cuda'), feature_dir=feature_dir)","metadata":{"execution":{"iopub.status.busy":"2023-07-16T16:37:43.374127Z","iopub.execute_input":"2023-07-16T16:37:43.374683Z","iopub.status.idle":"2023-07-16T16:38:04.121533Z","shell.execute_reply.started":"2023-07-16T16:37:43.374636Z","shell.execute_reply":"2023-07-16T16:38:04.120362Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Import into colmap\n\nfrom h5_to_db import import_into_colmap\n\ndatabase_path = f'{tgt}/database_disk.db'\n!rm -rf {database_path}\nimg_dir = f'{src}/images'\n\nimport_into_colmap(img_dir, database_path=database_path, feature_dir=feature_dir)","metadata":{"execution":{"iopub.status.busy":"2023-07-16T16:38:04.126994Z","iopub.execute_input":"2023-07-16T16:38:04.130031Z","iopub.status.idle":"2023-07-16T16:38:05.605169Z","shell.execute_reply.started":"2023-07-16T16:38:04.129971Z","shell.execute_reply":"2023-07-16T16:38:05.603534Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Now we run colmap. First matching - because it is bundled with RANSAC.\n# Don't worry, colmap would not match our features again, it will just estimate geometry.\noutput_path =  f'{tgt}/disk_reconstruction'\nos.makedirs(output_path, exist_ok=True)\npycolmap.match_exhaustive(database_path)\n\n# Then we run reconstruction, as before\nmaps = pycolmap.incremental_mapping(database_path, img_dir, output_path)","metadata":{"execution":{"iopub.status.busy":"2023-07-16T16:38:05.607402Z","iopub.execute_input":"2023-07-16T16:38:05.608118Z","iopub.status.idle":"2023-07-16T16:40:36.638591Z","shell.execute_reply.started":"2023-07-16T16:38:05.608064Z","shell.execute_reply":"2023-07-16T16:40:36.637091Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"maps","metadata":{"execution":{"iopub.status.busy":"2023-07-16T16:40:36.640499Z","iopub.execute_input":"2023-07-16T16:40:36.644200Z","iopub.status.idle":"2023-07-16T16:40:36.654694Z","shell.execute_reply.started":"2023-07-16T16:40:36.644163Z","shell.execute_reply":"2023-07-16T16:40:36.653381Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Inspect the largest map generated by DISK + colmap. \n# Note that it might produce more than one reconstruction.\n\nbest_index = None\nbest_num_reg_images = 0\nfor idx in maps:\n    if maps[idx].num_reg_images() > best_num_reg_images:\n        best_index = idx\n        best_num_reg_images = maps[idx].num_reg_images()\n\nif best_num_reg_images > 0:\n    print(f'Looking at reconstruction #{best_index} with {best_num_reg_images} registered images')\n    fig = viz_3d.init_figure()\n    viz_3d.plot_reconstruction(fig, maps[best_index], color='rgba(0,0,255,0.5)', name=\"Reconstruction\", cs=3, cameras=False)\n    viz_3d.plot_reconstruction(fig, maps[best_index], color='rgba(255,0,0,0.5)', name=\"Reconstruction\", cs=3, points=False)\n    fig.show()\nelse:\n    print('No reconstruction. :(')","metadata":{"execution":{"iopub.status.busy":"2023-07-16T16:40:36.657030Z","iopub.execute_input":"2023-07-16T16:40:36.657707Z","iopub.status.idle":"2023-07-16T16:40:37.087350Z","shell.execute_reply.started":"2023-07-16T16:40:36.657660Z","shell.execute_reply":"2023-07-16T16:40:37.086011Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Now we will try to align the obtained reconstruction to the GT one with Helmert transform\ncommon_fnames = []\nfor k, v in maps[best_index].images.items():\n    common_fnames.append(v.name)\n\ngt_positions = []\nfor img_fname in common_fnames:\n    for k,v in rec_gt.images.items():\n        if v.name == img_fname:\n            break\n    gt_positions.append(v.projection_center().reshape(3, 1))\n\nmin_common_images = 10\ntransform = maps[best_index].align_robust(common_fnames, gt_positions, 5)\nprint (transform)","metadata":{"execution":{"iopub.status.busy":"2023-07-16T16:40:37.089684Z","iopub.execute_input":"2023-07-16T16:40:37.090640Z","iopub.status.idle":"2023-07-16T16:40:37.113060Z","shell.execute_reply.started":"2023-07-16T16:40:37.090584Z","shell.execute_reply":"2023-07-16T16:40:37.111367Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Visualize the alignment.\n# Note that the ground truth will show more cameras than we provide you for the reconstruction.\n\nprint(rec_gt)\nprint()\n\nfig = viz_3d.init_figure()\nviz_3d.plot_reconstruction(fig, maps[best_index], color='rgba(0,255,255,0.5)', name=\"Reconstruction\", cs=5)\nviz_3d.plot_reconstruction(fig, rec_gt, points=False, color='rgba(0,100,0,0.1)', name=\"GT Reconstruction\", cs=5)\nfig.show()\n","metadata":{"execution":{"iopub.status.busy":"2023-07-16T16:40:37.115525Z","iopub.execute_input":"2023-07-16T16:40:37.116725Z","iopub.status.idle":"2023-07-16T16:40:39.839009Z","shell.execute_reply.started":"2023-07-16T16:40:37.116678Z","shell.execute_reply":"2023-07-16T16:40:39.837969Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}