!python -m pip install --no-deps /kaggle/input/dependencies-imc/pycolmap/pycolmap-0.4.0-cp310-cp310-manylinux2014_x86_64.whl
!python -m pip install --no-deps /kaggle/input/dependencies-imc/safetensors/safetensors-0.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
!python -m pip install --no-index --find-links=/kaggle/input/dependencies-imc/transformers/ transformers > /dev/null
!python -m pip install --no-deps /kaggle/input/imc2024-packages-lightglue-rerun-kornia/lightglue-0.0-py3-none-any.whl
# dkm
!python -m pip install --no-index --find-links=/kaggle/input/dkm-dependencies/packages einops > /dev/null
Processing /kaggle/input/dependencies-imc/pycolmap/pycolmap-0.4.0-cp310-cp310-manylinux2014_x86_64.whl
Installing collected packages: pycolmap
Successfully installed pycolmap-0.4.0
Processing /kaggle/input/dependencies-imc/safetensors/safetensors-0.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Installing collected packages: safetensors
Attempting uninstall: safetensors
Found existing installation: safetensors 0.4.3
Uninstalling safetensors-0.4.3:
Successfully uninstalled safetensors-0.4.3
Successfully installed safetensors-0.4.1
Processing /kaggle/input/imc2024-packages-lightglue-rerun-kornia/lightglue-0.0-py3-none-any.whl
Installing collected packages: lightglue
Successfully installed lightglue-0.0
# lightglue models
!mkdir -p /root/.cache/torch/hub/checkpoints
!cp /kaggle/input/aliked/pytorch/aliked-n16/1/* /root/.cache/torch/hub/checkpoints/
!cp /kaggle/input/lightglue/pytorch/aliked/1/* /root/.cache/torch/hub/checkpoints/
!cp /kaggle/input/lightglue/pytorch/aliked/1/aliked_lightglue.pth /root/.cache/torch/hub/checkpoints/aliked_lightglue_v0-1_arxiv-pth
!cp /kaggle/input/pytorch-lightglue-models/* /root/.cache/torch/hub/checkpoints/
# dkm model
!mkdir -p /root/.cache/torch/hub/checkpoints
!cp /kaggle/input/dkm-dependencies/DKMv3_outdoor.pth /root/.cache/torch/hub/checkpoints/
%matplotlib inline
# General utilities
import os
from tqdm import tqdm
from time import time
from fastprogress import progress_bar
import gc
import numpy as np
import pandas as pd
import h5py
from IPython.display import clear_output
from collections import defaultdict
from copy import deepcopy
# CV/ML
import cv2
import torch
import torch.nn.functional as F
from torch.utils.data import Dataset, DataLoader
import kornia as K
import kornia.feature as KF
from PIL import Image
import timm
from timm.data import resolve_data_config
from timm.data.transforms_factory import create_transform
import torchvision
# 3D reconstruction
import pycolmap
import glob
import matplotlib
from matplotlib import pyplot as plt
# dkm
import sys
sys.path.append('/kaggle/input/dkm-dependencies/DKM/')
from dkm.utils.utils import tensor_to_pil, get_tuple_transform_ops
from dkm import DKMv3_outdoor
# LoFTR
from kornia.feature import LoFTR
from lightglue import match_pair
from lightglue import ALIKED, SuperPoint, DoGHardNet, LightGlue
from lightglue.utils import load_image, rbd
from kornia.feature import LoFTR
print('Kornia version', K.__version__)
print('Pycolmap version', pycolmap.__version__)
Kornia version 0.7.2 Pycolmap version 0.4.0
class CONFIG:
use_aliked_lightglue = True
use_doghardnet_lightglue = False
use_superpoint_lightglue = True
use_loftr = False
use_dkm = False
use_superglue = False
params_aliked_lightglue = {
"num_features" : 8192,
"detection_threshold" : 0.005,
"min_matches" : 100,
"resize_to" : 1280,
}
params_doghardnet_lightglue = {
"num_features" : 8192,
"detection_threshold" : 0.001,
"min_matches" : 15,
"resize_to" : 1024,
}
params_superpoint_lightglue = {
"num_features" : 4096,
"detection_threshold" : 0.005,
"min_matches" : 15,
"resize_to" : 1280,
}
params_loftr = {
"resize_small_edge_to" : 750,
"min_matches" : 15,
}
params_dkm = {
"num_features" : 2048,
"detection_threshold" : 0.4,
"min_matches" : 15,
"resize_to" : (540, 720),
}
params_sg = {
"sg_config" :
{
"superpoint": {
"nms_radius": 4,
"keypoint_threshold": 0.0001,
"max_keypoints": 4096
},
"superglue": {
"weights": "outdoor",
"sinkhorn_iterations": 10,
"match_threshold": 0.2,
},
},
"resize_to": 1240,
"min_matches": 15,
}
device=torch.device('cuda')
# Code to manipulate a colmap database.
# Forked from https://github.com/colmap/colmap/blob/dev/scripts/python/database.py
# Copyright (c) 2018, ETH Zurich and UNC Chapel Hill.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of
# its contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# Author: Johannes L. Schoenberger (jsch-at-demuc-dot-de)
# This script is based on an original implementation by True Price.
import sys
import sqlite3
import numpy as np
IS_PYTHON3 = sys.version_info[0] >= 3
MAX_IMAGE_ID = 2**31 - 1
CREATE_CAMERAS_TABLE = """CREATE TABLE IF NOT EXISTS cameras (
camera_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
model INTEGER NOT NULL,
width INTEGER NOT NULL,
height INTEGER NOT NULL,
params BLOB,
prior_focal_length INTEGER NOT NULL)"""
CREATE_DESCRIPTORS_TABLE = """CREATE TABLE IF NOT EXISTS descriptors (
image_id INTEGER PRIMARY KEY NOT NULL,
rows INTEGER NOT NULL,
cols INTEGER NOT NULL,
data BLOB,
FOREIGN KEY(image_id) REFERENCES images(image_id) ON DELETE CASCADE)"""
CREATE_IMAGES_TABLE = """CREATE TABLE IF NOT EXISTS images (
image_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
name TEXT NOT NULL UNIQUE,
camera_id INTEGER NOT NULL,
prior_qw REAL,
prior_qx REAL,
prior_qy REAL,
prior_qz REAL,
prior_tx REAL,
prior_ty REAL,
prior_tz REAL,
CONSTRAINT image_id_check CHECK(image_id >= 0 and image_id < {}),
FOREIGN KEY(camera_id) REFERENCES cameras(camera_id))
""".format(MAX_IMAGE_ID)
CREATE_TWO_VIEW_GEOMETRIES_TABLE = """
CREATE TABLE IF NOT EXISTS two_view_geometries (
pair_id INTEGER PRIMARY KEY NOT NULL,
rows INTEGER NOT NULL,
cols INTEGER NOT NULL,
data BLOB,
config INTEGER NOT NULL,
F BLOB,
E BLOB,
H BLOB)
"""
CREATE_KEYPOINTS_TABLE = """CREATE TABLE IF NOT EXISTS keypoints (
image_id INTEGER PRIMARY KEY NOT NULL,
rows INTEGER NOT NULL,
cols INTEGER NOT NULL,
data BLOB,
FOREIGN KEY(image_id) REFERENCES images(image_id) ON DELETE CASCADE)
"""
CREATE_MATCHES_TABLE = """CREATE TABLE IF NOT EXISTS matches (
pair_id INTEGER PRIMARY KEY NOT NULL,
rows INTEGER NOT NULL,
cols INTEGER NOT NULL,
data BLOB)"""
CREATE_NAME_INDEX = \
"CREATE UNIQUE INDEX IF NOT EXISTS index_name ON images(name)"
CREATE_ALL = "; ".join([
CREATE_CAMERAS_TABLE,
CREATE_IMAGES_TABLE,
CREATE_KEYPOINTS_TABLE,
CREATE_DESCRIPTORS_TABLE,
CREATE_MATCHES_TABLE,
CREATE_TWO_VIEW_GEOMETRIES_TABLE,
CREATE_NAME_INDEX
])
def image_ids_to_pair_id(image_id1, image_id2):
if image_id1 > image_id2:
image_id1, image_id2 = image_id2, image_id1
return image_id1 * MAX_IMAGE_ID + image_id2
def pair_id_to_image_ids(pair_id):
image_id2 = pair_id % MAX_IMAGE_ID
image_id1 = (pair_id - image_id2) / MAX_IMAGE_ID
return image_id1, image_id2
def array_to_blob(array):
if IS_PYTHON3:
return array.tostring()
else:
return np.getbuffer(array)
def blob_to_array(blob, dtype, shape=(-1,)):
if IS_PYTHON3:
return np.fromstring(blob, dtype=dtype).reshape(*shape)
else:
return np.frombuffer(blob, dtype=dtype).reshape(*shape)
class COLMAPDatabase(sqlite3.Connection):
@staticmethod
def connect(database_path):
return sqlite3.connect(database_path, factory=COLMAPDatabase)
def __init__(self, *args, **kwargs):
super(COLMAPDatabase, self).__init__(*args, **kwargs)
self.create_tables = lambda: self.executescript(CREATE_ALL)
self.create_cameras_table = \
lambda: self.executescript(CREATE_CAMERAS_TABLE)
self.create_descriptors_table = \
lambda: self.executescript(CREATE_DESCRIPTORS_TABLE)
self.create_images_table = \
lambda: self.executescript(CREATE_IMAGES_TABLE)
self.create_two_view_geometries_table = \
lambda: self.executescript(CREATE_TWO_VIEW_GEOMETRIES_TABLE)
self.create_keypoints_table = \
lambda: self.executescript(CREATE_KEYPOINTS_TABLE)
self.create_matches_table = \
lambda: self.executescript(CREATE_MATCHES_TABLE)
self.create_name_index = lambda: self.executescript(CREATE_NAME_INDEX)
def add_camera(self, model, width, height, params,
prior_focal_length=False, camera_id=None):
params = np.asarray(params, np.float64)
cursor = self.execute(
"INSERT INTO cameras VALUES (?, ?, ?, ?, ?, ?)",
(camera_id, model, width, height, array_to_blob(params),
prior_focal_length))
return cursor.lastrowid
def add_image(self, name, camera_id,
prior_q=np.zeros(4), prior_t=np.zeros(3), image_id=None):
cursor = self.execute(
"INSERT INTO images VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
(image_id, name, camera_id, prior_q[0], prior_q[1], prior_q[2],
prior_q[3], prior_t[0], prior_t[1], prior_t[2]))
return cursor.lastrowid
def add_keypoints(self, image_id, keypoints):
assert(len(keypoints.shape) == 2)
assert(keypoints.shape[1] in [2, 4, 6])
keypoints = np.asarray(keypoints, np.float32)
self.execute(
"INSERT INTO keypoints VALUES (?, ?, ?, ?)",
(image_id,) + keypoints.shape + (array_to_blob(keypoints),))
def add_descriptors(self, image_id, descriptors):
descriptors = np.ascontiguousarray(descriptors, np.uint8)
self.execute(
"INSERT INTO descriptors VALUES (?, ?, ?, ?)",
(image_id,) + descriptors.shape + (array_to_blob(descriptors),))
def add_matches(self, image_id1, image_id2, matches):
assert(len(matches.shape) == 2)
assert(matches.shape[1] == 2)
if image_id1 > image_id2:
matches = matches[:,::-1]
pair_id = image_ids_to_pair_id(image_id1, image_id2)
matches = np.asarray(matches, np.uint32)
self.execute(
"INSERT INTO matches VALUES (?, ?, ?, ?)",
(pair_id,) + matches.shape + (array_to_blob(matches),))
def add_two_view_geometry(self, image_id1, image_id2, matches,
F=np.eye(3), E=np.eye(3), H=np.eye(3), config=2):
assert(len(matches.shape) == 2)
assert(matches.shape[1] == 2)
if image_id1 > image_id2:
matches = matches[:,::-1]
pair_id = image_ids_to_pair_id(image_id1, image_id2)
matches = np.asarray(matches, np.uint32)
F = np.asarray(F, dtype=np.float64)
E = np.asarray(E, dtype=np.float64)
H = np.asarray(H, dtype=np.float64)
self.execute(
"INSERT INTO two_view_geometries VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
(pair_id,) + matches.shape + (array_to_blob(matches), config,
array_to_blob(F), array_to_blob(E), array_to_blob(H)))
# Code to interface DISK with Colmap.
# Forked from https://github.com/cvlab-epfl/disk/blob/37f1f7e971cea3055bb5ccfc4cf28bfd643fa339/colmap/h5_to_db.py
# Copyright [2020] [Michał Tyszkiewicz, Pascal Fua, Eduard Trulls]
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os, argparse, h5py, warnings
import numpy as np
from tqdm import tqdm
from PIL import Image, ExifTags
def get_focal(image_path, err_on_default=False):
image = Image.open(image_path)
max_size = max(image.size)
exif = image.getexif()
focal = None
if exif is not None:
focal_35mm = None
# https://github.com/colmap/colmap/blob/d3a29e203ab69e91eda938d6e56e1c7339d62a99/src/util/bitmap.cc#L299
for tag, value in exif.items():
focal_35mm = None
if ExifTags.TAGS.get(tag, None) == 'FocalLengthIn35mmFilm':
focal_35mm = float(value)
break
if focal_35mm is not None:
focal = focal_35mm / 35. * max_size
if focal is None:
if err_on_default:
raise RuntimeError("Failed to find focal length")
# failed to find it in exif, use prior
FOCAL_PRIOR = 1.2
focal = FOCAL_PRIOR * max_size
return focal
def create_camera(db, image_path, camera_model):
image = Image.open(image_path)
width, height = image.size
focal = get_focal(image_path)
if camera_model == 'simple-pinhole':
model = 0 # simple pinhole
param_arr = np.array([focal, width / 2, height / 2])
if camera_model == 'pinhole':
model = 1 # pinhole
param_arr = np.array([focal, focal, width / 2, height / 2])
elif camera_model == 'simple-radial':
model = 2 # simple radial
param_arr = np.array([focal, width / 2, height / 2, 0.1])
elif camera_model == 'opencv':
model = 4 # opencv
param_arr = np.array([focal, focal, width / 2, height / 2, 0., 0., 0., 0.])
return db.add_camera(model, width, height, param_arr)
def add_keypoints(db, h5_path, image_path, img_ext, camera_model, single_camera = True):
keypoint_f = h5py.File(os.path.join(h5_path, 'keypoints.h5'), 'r')
camera_id = None
fname_to_id = {}
for filename in tqdm(list(keypoint_f.keys())):
keypoints = keypoint_f[filename][()]
fname_with_ext = filename# + img_ext
path = os.path.join(image_path, fname_with_ext)
if not os.path.isfile(path):
raise IOError(f'Invalid image path {path}')
if camera_id is None or not single_camera:
camera_id = create_camera(db, path, camera_model)
image_id = db.add_image(fname_with_ext, camera_id)
fname_to_id[filename] = image_id
db.add_keypoints(image_id, keypoints)
return fname_to_id
def add_matches(db, h5_path, fname_to_id):
match_file = h5py.File(os.path.join(h5_path, 'matches.h5'), 'r')
added = set()
n_keys = len(match_file.keys())
n_total = (n_keys * (n_keys - 1)) // 2
with tqdm(total=n_total) as pbar:
for key_1 in match_file.keys():
group = match_file[key_1]
for key_2 in group.keys():
id_1 = fname_to_id[key_1]
id_2 = fname_to_id[key_2]
pair_id = image_ids_to_pair_id(id_1, id_2)
if pair_id in added:
warnings.warn(f'Pair {pair_id} ({id_1}, {id_2}) already added!')
continue
matches = group[key_2][()]
db.add_matches(id_1, id_2, matches)
added.add(pair_id)
pbar.update(1)
def import_into_colmap(img_dir,
feature_dir ='.featureout',
database_path = 'colmap.db',
img_ext='.jpg'):
db = COLMAPDatabase.connect(database_path)
db.create_tables()
single_camera = False
fname_to_id = add_keypoints(db, feature_dir, img_dir, img_ext, 'simple-radial', single_camera)
add_matches(
db,
feature_dir,
fname_to_id,
)
db.commit()
return
# We will use ViT global descriptor to get matching shortlists.
def get_global_desc(fnames, model,
device = torch.device('cpu')):
model = model.eval()
model= model.to(device)
config = resolve_data_config({}, model=model)
transform = create_transform(**config)
global_descs_convnext=[]
for i, img_fname_full in tqdm(enumerate(fnames),total= len(fnames)):
key = os.path.splitext(os.path.basename(img_fname_full))[0]
img = Image.open(img_fname_full).convert('RGB')
timg = transform(img).unsqueeze(0).to(device)
with torch.no_grad(), torch.cuda.amp.autocast():
desc = model.forward_features(timg.to(device)).mean(dim=(-1,2))#
#print (desc.shape)
desc = desc.view(1, -1)
desc_norm = F.normalize(desc, dim=1, p=2)
#print (desc_norm)
global_descs_convnext.append(desc_norm.detach().cpu())
global_descs_all = torch.cat(global_descs_convnext, dim=0)
return global_descs_all.to(torch.float32)
def convert_1d_to_2d(idx, num_images):
idx1 = idx // num_images
idx2 = idx % num_images
return (idx1, idx2)
def get_pairs_from_distancematrix(mat):
pairs = [ convert_1d_to_2d(idx, mat.shape[0]) for idx in np.argsort(mat.flatten())]
pairs = [ pair for pair in pairs if pair[0] < pair[1] ]
return pairs
def get_img_pairs_exhaustive(img_fnames, model, device):
#index_pairs = []
#for i in range(len(img_fnames)):
# for j in range(i+1, len(img_fnames)):
# index_pairs.append((i,j))
#return index_pairs
descs = get_global_desc(img_fnames, model, device=device)
dm = torch.cdist(descs, descs, p=2).detach().cpu().numpy()
matching_list = get_pairs_from_distancematrix(dm)
return matching_list
def get_image_pairs_shortlist(fnames,
sim_th = 0.6, # should be strict
min_pairs = 20,
exhaustive_if_less = 20,
device=torch.device('cpu')):
num_imgs = len(fnames)
model = timm.create_model('tf_efficientnet_b7',
checkpoint_path='/kaggle/input/tf-efficientnet/pytorch/tf-efficientnet-b7/1/tf_efficientnet_b7_ra-6c08e654.pth')
model.eval()
descs = get_global_desc(fnames, model, device=device)
if num_imgs <= exhaustive_if_less:
return get_img_pairs_exhaustive(fnames, model, device)
dm = torch.cdist(descs, descs, p=2).detach().cpu().numpy()
# removing half
mask = dm <= sim_th
total = 0
matching_list = []
ar = np.arange(num_imgs)
already_there_set = []
for st_idx in range(num_imgs-1):
mask_idx = mask[st_idx]
to_match = ar[mask_idx]
if len(to_match) < min_pairs:
to_match = np.argsort(dm[st_idx])[:min_pairs]
for idx in to_match:
if st_idx == idx:
continue
if dm[st_idx, idx] < 1000:
matching_list.append(tuple(sorted((st_idx, idx.item()))))
total+=1
matching_list = sorted(list(set(matching_list)))
return matching_list
def load_torch_image(fname, device=torch.device('cpu')):
img = K.io.load_image(fname, K.io.ImageLoadType.RGB32, device=device)[None, ...]
return img
def detect_common(img_fnames,
model_name,
feature_dir = '.featureout',
num_features = 4096,
resize_to = 1024,
detection_threshold = 0.01,
device=torch.device('cpu')):
dict_model = {
"aliked" : ALIKED,
"superpoint" : SuperPoint,
"doghardnet" : DoGHardNet,
}
extractor_class = dict_model[model_name]
dtype = torch.float32 # ALIKED has issues with float16
extractor = extractor_class(
max_num_keypoints=num_features, detection_threshold=detection_threshold, resize=resize_to
).eval().to(device, dtype)
if not os.path.isdir(feature_dir):
os.makedirs(feature_dir)
with h5py.File(f'{feature_dir}/keypoints_{model_name}.h5', mode='w') as f_kp, \
h5py.File(f'{feature_dir}/descriptors_{model_name}.h5', mode='w') as f_desc:
for img_path in tqdm(img_fnames):
img_fname = img_path.split('/')[-1]
key = img_fname
with torch.inference_mode():
image0 = load_torch_image(img_path, device=device).to(dtype)
feats0 = extractor.extract(image0) # auto-resize the image, disable with resize=None
kpts = feats0['keypoints'].reshape(-1, 2).detach().cpu().numpy()
descs = feats0['descriptors'].reshape(len(kpts), -1).detach().cpu().numpy()
f_kp[key] = kpts
f_desc[key] = descs
print(f"{model_name} > kpts.shape={kpts.shape}, descs.shape={descs.shape}")
return
def match_with_lightglue_common(img_fnames, model_name,
index_pairs, file_keypoints,
feature_dir = '.featureout',
device=torch.device('cpu'),
min_matches=15,verbose=True):
lg_matcher = KF.LightGlueMatcher(model_name, {"width_confidence": -1,
"depth_confidence": -1,
"mp": True if 'cuda' in str(device) else False}).eval().to(device)
cnt_pairs = 0
with h5py.File(f'{feature_dir}/keypoints_{model_name}.h5', mode='r') as f_kp, \
h5py.File(f'{feature_dir}/descriptors_{model_name}.h5', mode='r') as f_desc, \
h5py.File(file_keypoints, mode='w') as f_match:
for pair_idx in tqdm(index_pairs):
idx1, idx2 = pair_idx
fname1, fname2 = img_fnames[idx1], img_fnames[idx2]
key1, key2 = fname1.split('/')[-1], fname2.split('/')[-1]
kp1 = torch.from_numpy(f_kp[key1][...]).to(device)
kp2 = torch.from_numpy(f_kp[key2][...]).to(device)
desc1 = torch.from_numpy(f_desc[key1][...]).to(device)
desc2 = torch.from_numpy(f_desc[key2][...]).to(device)
with torch.inference_mode():
dists, idxs = lg_matcher(desc1,
desc2,
KF.laf_from_center_scale_ori(kp1[None]),
KF.laf_from_center_scale_ori(kp2[None]))
if len(idxs) == 0:
continue
n_matches = len(idxs)
kp1 = kp1[idxs[:,0], :].cpu().numpy().reshape(-1, 2).astype(np.float32)
kp2 = kp2[idxs[:,1], :].cpu().numpy().reshape(-1, 2).astype(np.float32)
group = f_match.require_group(key1)
if n_matches >= min_matches:
group.create_dataset(key2, data=np.concatenate([kp1, kp2], axis=1))
cnt_pairs+=1
print (f'{key1}-{key2}: {n_matches} matches @ {cnt_pairs}th pair({model_name}+lightglue)')
else:
print (f'{key1}-{key2}: {n_matches} matches --> skipped')
return
def detect_lightglue_common(
img_fnames, model_name, index_pairs, feature_dir, device, file_keypoints,
resize_to=1024,
detection_threshold=0.01,
num_features=4096,
min_matches=15,
):
t=time()
detect_common(
img_fnames, model_name, feature_dir,
resize_to=resize_to,
num_features=num_features,
detection_threshold=detection_threshold,
device=device
)
gc.collect()
match_with_lightglue_common(
img_fnames, model_name, index_pairs, file_keypoints,
feature_dir=feature_dir,
min_matches=min_matches,
device=device
)
t=time() -t
print(f'Features matched in {t:.4f} sec ({model_name}+LightGlue)')
return t
import sys
sys.path.append("../input/super-glue-pretrained-network")
from models.matching import Matching
from models.utils import (compute_pose_error, compute_epipolar_error,
estimate_pose, make_matching_plot,
error_colormap, AverageTimer, pose_auc, read_image,
process_resize, frame2tensor,
rotate_intrinsics, rotate_pose_inplane,
scale_intrinsics)
from torch.nn import functional as torchF # For resizing tensor
def sg_imread(path):
image = cv2.imread(str(path), cv2.IMREAD_GRAYSCALE)
return image
# Preprocess
def sg_read_image(image, device, resize):
w, h = image.shape[1], image.shape[0]
w_new, h_new = process_resize(w, h, [resize,])
unit_shape = 8
w_new = w_new // unit_shape * unit_shape
h_new = h_new // unit_shape * unit_shape
scales = (float(w) / float(w_new), float(h) / float(h_new))
image = cv2.resize(image.astype('float32'), (w_new, h_new))
inp = frame2tensor(image, "cpu")
return image, inp, scales, (h, w)
class SGDataset(Dataset):
def __init__(self, fnames1, fnames2, resize_to, device):
self.fnames1 = fnames1
self.fnames2 = fnames2
self.resize_to = resize_to
self.device = device
def __len__(self):
return len(self.fnames1)
def __getitem__(self, idx):
fname1 = self.fnames1[idx]
fname2 = self.fnames2[idx]
im1, im2 = cv2.imread(fname1, cv2.IMREAD_GRAYSCALE), cv2.imread(fname2, cv2.IMREAD_GRAYSCALE)
_, image1, scale1, ori_shape1 = sg_read_image(im1, self.device, self.resize_to)
_, image2, scale2, ori_shape2 = sg_read_image(im2, self.device, self.resize_to)
return image1, image2, torch.tensor([idx]), torch.tensor(ori_shape1), torch.tensor(ori_shape2)
def get_superglue_dataloader(images1, images2, resize_to, device, batch_size=1):
dataset = SGDataset(images1, images2, resize_to, device)
dataloader = DataLoader(
dataset=dataset,
shuffle=False,
batch_size=batch_size,
pin_memory=True,
num_workers=2,
drop_last=False
)
return dataloader
def detect_superglue(
img_fnames, index_pairs, feature_dir, device, sg_config, file_keypoints,
resize_to=750, min_matches=15
):
t=time()
matcher_superglue = Matching(sg_config).eval().to(device)
fnames1, fnames2, idxs1, idxs2 = [], [], [], []
for pair_idx in progress_bar(index_pairs):
idx1, idx2 = pair_idx
fname1, fname2 = img_fnames[idx1], img_fnames[idx2]
fnames1.append(fname1)
fnames2.append(fname2)
idxs1.append(idx1)
idxs2.append(idx2)
dataloader = get_superglue_dataloader( fnames1, fnames2, resize_to, device)
cnt_pairs = 0
with h5py.File(file_keypoints, mode='w') as f_match:
for X in dataloader:
image1, image2, idx, ori_shape_1, ori_shape_2 = X
fname1, fname2 = fnames1[idx], fnames2[idx]
key1, key2 = fname1.split('/')[-1], fname2.split('/')[-1]
pred = matcher_superglue({"image0": image1[0].to(device), "image1": image2[0].to(device)})
pred = {k: v[0].detach().cpu().numpy().copy() for k, v in pred.items()}
mkpts1, mkpts2 = pred["keypoints0"], pred["keypoints1"]
matches, conf = pred["matches0"], pred["matching_scores0"]
valid = matches > -1
mkpts1 = mkpts1[valid]
mkpts2 = mkpts2[matches[valid]]
mconf = conf[valid]
ori_shape_1 = ori_shape_1[0].numpy()
ori_shape_2 = ori_shape_2[0].numpy()
# Scaling coords
mkpts1[:,0] = mkpts1[:,0] * ori_shape_1[1] / image1[0].shape[3] # X
mkpts1[:,1] = mkpts1[:,1] * ori_shape_1[0] / image1[0].shape[2] # Y
mkpts2[:,0] = mkpts2[:,0] * ori_shape_2[1] / image2[0].shape[3] # X
mkpts2[:,1] = mkpts2[:,1] * ori_shape_2[0] / image2[0].shape[2] # Y
n_matches = mconf.shape[0]
group = f_match.require_group(key1)
if n_matches >= min_matches:
group.create_dataset(key2, data=np.concatenate([mkpts1, mkpts2], axis=1).astype(np.float32))
cnt_pairs+=1
print (f'{key1}-{key2}: {n_matches} matches @ {cnt_pairs}th pair(superglue)')
else:
print (f'{key1}-{key2}: {n_matches} matches --> skipped')
gc.collect()
t=time() -t
print(f'Features matched in {t:.4f} sec')
return t
class DKMDataset(Dataset):
def __init__(self, fnames1, fnames2, resize_to, device):
self.fnames1 = fnames1
self.fnames2 = fnames2
self.resize_to = resize_to
self.device = device
self.test_transform = get_tuple_transform_ops(
resize=self.resize_to, normalize=True
)
def __len__(self):
return len(self.fnames1)
def __getitem__(self, idx):
fname1 = self.fnames1[idx]
fname2 = self.fnames2[idx]
im1, im2 = Image.open(fname1), Image.open(fname2)
ori_shape_1 = im1.size
ori_shape_2 = im2.size
image1, image2 = self.test_transform((im1, im2))
return image1, image2, torch.tensor([idx]), torch.tensor(ori_shape_1), torch.tensor(ori_shape_2)
def get_dkm_dataloader(images1, images2, resize_to, device, batch_size=4):
dataset = DKMDataset(images1, images2, resize_to, device)
dataloader = DataLoader(
dataset=dataset,
shuffle=False,
batch_size=batch_size,
pin_memory=True,
num_workers=2,
drop_last=False
)
return dataloader
def get_dkm_mkpts(dkm_model, bimgs1, bimgs2, shapes1, shapes2, detection_threshold=0.5, num_features = 2000, min_matches=15):
dense_matches, dense_certainty = dkm_model.match(bimgs1, bimgs2, batched=True)
print("***", dense_matches.shape, dense_certainty.shape)
store_mkpts1, store_mkpts2, store_mconf = [], [], []
# drop low confidence pairs
for b in range(dense_matches.shape[0]):
u_dense_matches = dense_matches[b, dense_certainty[b,...].sqrt() >= detection_threshold, :]
u_dense_certainty = dense_certainty[b, dense_certainty[b,...].sqrt() >= detection_threshold]
if u_dense_matches.shape[0] > num_features:
u_dense_matches, u_dense_certainty = dkm_model.sample( u_dense_matches, u_dense_certainty, num=num_features)
u_dense_matches = u_dense_matches.reshape((-1, 4))
u_dense_certainty = u_dense_certainty.reshape((-1,))
mkpts1 = u_dense_matches[:, :2]
mkpts2 = u_dense_matches[:, 2:]
w1, h1 = shapes1[b, :]
w2, h2 = shapes2[b, :]
mkpts1[:, 0] = ((mkpts1[:, 0] + 1)/2) * w1
mkpts1[:, 1] = ((mkpts1[:, 1] + 1)/2) * h1
mkpts2[:, 0] = ((mkpts2[:, 0] + 1)/2) * w2
mkpts2[:, 1] = ((mkpts2[:, 1] + 1)/2) * h2
mkpts1 = mkpts1.cpu().detach().numpy()
mkpts2 = mkpts2.cpu().detach().numpy()
mconf = u_dense_certainty.sqrt().cpu().detach().numpy()
if mconf.shape[0] > min_matches:
try:
# calc Fundamental matrix from keypoints
F, inliers = cv2.findFundamentalMat(mkpts1, mkpts2, cv2.USAC_MAGSAC, 0.200, 0.999, 2000)
inliers = inliers > 0
mkpts1 = mkpts1[inliers[:,0]]
mkpts2 = mkpts2[inliers[:,0]]
mconf = mconf[inliers[:,0]]
#print("---", mconf.shape)
if mconf.shape[0] > 3000:
rand_idx = np.random.choice(range(mconf.shape[0]), 3000, replace=False)
mkpts1 = mkpts1[rand_idx, :]
mkpts2 = mkpts2[rand_idx, :]
mconf = mconf[rand_idx]
except:
mkpts1 = np.empty((0,2))
mkpts2 = np.empty((0,2))
mconf = np.empty((0,))
store_mkpts1.append(mkpts1)
store_mkpts2.append(mkpts2)
store_mconf.append(mconf)
return store_mkpts1, store_mkpts2, store_mconf
def detect_dkm(
img_fnames, index_pairs, feature_dir, device,
resize_to=(540, 720),
detection_threshold=0.4,
num_features=2000,
min_matches=15,
):
t=time()
dkm_model = DKMv3_outdoor(device=device)
dkm_model.upsample_preds=False
fnames1, fnames2 = [], []
for pair_idx in progress_bar(index_pairs):
idx1, idx2 = pair_idx
fname1, fname2 = img_fnames[idx1], img_fnames[idx2]
fnames1.append(fname1)
fnames2.append(fname2)
cnt_pairs = 0
with h5py.File(f'{feature_dir}/matches_dkm.h5', mode='w') as f_match:
dataloader = get_dkm_dataloader(fnames1, fnames2, resize_to, device, batch_size=4)
for X in tqdm(dataloader):
images1, images2, idxs, shapes1, shapes2 = X
store_mkpts1, store_mkpts2, store_mconf = get_dkm_mkpts(
dkm_model, images1.to(device), images2.to(device), shapes1, shapes2,
detection_threshold=detection_threshold, num_features = num_features, min_matches=min_matches,
)
for b in range(images1.shape[0]):
mkpts1 = store_mkpts1[b]
mkpts2 = store_mkpts2[b]
mconf = store_mconf[b]
file1 = fnames1[idxs[b]]
file2 = fnames2[idxs[b]]
key1, key2 = file1.split('/')[-1], file2.split('/')[-1]
n_matches = mconf.shape[0]
print (f'{key1}-{key2}: {n_matches} matches @ {cnt_pairs}th pair(dkm)')
group = f_match.require_group(key1)
if n_matches >= min_matches:
group.create_dataset(key2, data=np.concatenate([mkpts1, mkpts2], axis=1).astype(np.float32))
cnt_pairs+=1
gc.collect()
t=time() -t
print(f'Features matched in {t:.4f} sec')
return t
class LoFTRDataset(Dataset):
def __init__(self, fnames1, fnames2, idxs1, idxs2, resize_small_edge_to, device):
self.fnames1 = fnames1
self.fnames2 = fnames2
self.keys1 = [ fname.split('/')[-1] for fname in fnames1 ]
self.keys2 = [ fname.split('/')[-1] for fname in fnames2 ]
self.idxs1 = idxs1
self.idxs2 = idxs2
self.resize_small_edge_to = resize_small_edge_to
self.device = device
self.round_unit = 16
def __len__(self):
return len(self.images1)
def load_torch_image(self, fname, device):
img = cv2.imread(fname)
original_shape = img.shape
ratio = self.resize_small_edge_to / min([img.shape[0], img.shape[1]])
w = int(img.shape[1] * ratio) # int( (img.shape[1] * ratio) // self.round_unit * self.round_unit )
h = int(img.shape[0] * ratio) # int( (img.shape[0] * ratio) // self.round_unit * self.round_unit )
img_resized = cv2.resize(img, (w, h))
img_resized = K.image_to_tensor(img_resized, False).float() /255.
img_resized = K.color.bgr_to_rgb(img_resized)
img_resized = K.color.rgb_to_grayscale(img_resized)
return img_resized.to(device), original_shape
def __getitem__(self, idx):
fname1 = self.fnames1[idx]
fname2 = self.fnames2[idx]
image1, ori_shape_1 = self.load_torch_image(fname1, device)
image2, ori_shape_2 = self.load_torch_image(fname2, device)
return image1, image2, self.keys1[idx], self.keys2[idx], self.idxs1[idx], self.idxs2[idx], ori_shape_1, ori_shape_2
def get_loftr_dataloader(images1, images2, idxs1, idxs2, resize_small_edge_to, device, batch_size=1):
dataset = LoFTRDataset(images1, images2, idxs1, idxs2, resize_small_edge_to, device)
dataloader = DataLoader(
dataset=dataset,
shuffle=False,
batch_size=batch_size,
pin_memory=True,
num_workers=2,
drop_last=False
)
return dataset
def detect_loftr(img_fnames, index_pairs, feature_dir, device, file_keypoints, resize_small_edge_to=750, min_matches=15):
t=time()
matcher = LoFTR(pretrained=None)
matcher.load_state_dict(torch.load("../input/loftr/pytorch/outdoor/1/loftr_outdoor.ckpt")['state_dict'])
matcher = matcher.to(device).eval()
fnames1, fnames2, idxs1, idxs2 = [], [], [], []
for pair_idx in progress_bar(index_pairs):
idx1, idx2 = pair_idx
fname1, fname2 = img_fnames[idx1], img_fnames[idx2]
fnames1.append(fname1)
fnames2.append(fname2)
idxs1.append(idx1)
idxs2.append(idx2)
dataloader = get_loftr_dataloader( fnames1, fnames2, idxs1, idxs2, resize_small_edge_to, device)
cnt_pairs = 0
with h5py.File(file_keypoints, mode='w') as f_match:
store_mkpts = {}
for X in tqdm(dataloader):
image1, image2, key1, key2, idx1, idx2, ori_shape_1, ori_shape_2 = X
fname1, fname2 = img_fnames[idx1], img_fnames[idx2]
with torch.no_grad():
correspondences = matcher( {"image0": image1.to(device),"image1": image2.to(device)} )
mkpts1 = correspondences['keypoints0'].cpu().numpy()
mkpts2 = correspondences['keypoints1'].cpu().numpy()
mconf = correspondences['confidence'].cpu().numpy()
mkpts1[:,0] *= (float(ori_shape_1[1]) / float(image1.shape[3]))
mkpts1[:,1] *= (float(ori_shape_1[0]) / float(image1.shape[2]))
mkpts2[:,0] *= (float(ori_shape_2[1]) / float(image2.shape[3]))
mkpts2[:,1] *= (float(ori_shape_2[0]) / float(image2.shape[2]))
n_matches = mconf.shape[0]
group = f_match.require_group(key1)
if n_matches >= min_matches:
group.create_dataset(key2, data=np.concatenate([mkpts1, mkpts2], axis=1).astype(np.float32))
cnt_pairs+=1
print (f'{key1}-{key2}: {n_matches} matches @ {cnt_pairs}th pair(loftr)')
else:
print (f'{key1}-{key2}: {n_matches} matches --> skipped')
gc.collect()
t=time() -t
print(f'Features matched in {t:.4f} sec')
return t
class DKMDataset(Dataset):
def __init__(self, fnames1, fnames2, resize_to, device):
self.fnames1 = fnames1
self.fnames2 = fnames2
self.resize_to = resize_to
self.device = device
self.test_transform = get_tuple_transform_ops(
resize=self.resize_to, normalize=True
)
def __len__(self):
return len(self.fnames1)
def __getitem__(self, idx):
fname1 = self.fnames1[idx]
fname2 = self.fnames2[idx]
im1, im2 = Image.open(fname1), Image.open(fname2)
ori_shape_1 = im1.size
ori_shape_2 = im2.size
image1, image2 = self.test_transform((im1, im2))
return image1, image2, torch.tensor([idx]), torch.tensor(ori_shape_1), torch.tensor(ori_shape_2)
def get_dkm_dataloader(images1, images2, resize_to, device, batch_size=4):
dataset = DKMDataset(images1, images2, resize_to, device)
dataloader = DataLoader(
dataset=dataset,
shuffle=False,
batch_size=batch_size,
pin_memory=True,
num_workers=2,
drop_last=False
)
return dataloader
def get_dkm_mkpts(dkm_model, bimgs1, bimgs2, shapes1, shapes2, detection_threshold=0.5, num_features = 2000, min_matches=15):
dense_matches, dense_certainty = dkm_model.match(bimgs1, bimgs2, batched=True)
store_mkpts1, store_mkpts2, store_mconf = [], [], []
# drop low confidence pairs
for b in range(dense_matches.shape[0]):
u_dense_matches = dense_matches[b, dense_certainty[b,...].sqrt() >= detection_threshold, :]
u_dense_certainty = dense_certainty[b, dense_certainty[b,...].sqrt() >= detection_threshold]
if u_dense_matches.shape[0] > num_features:
u_dense_matches, u_dense_certainty = dkm_model.sample( u_dense_matches, u_dense_certainty, num=num_features)
u_dense_matches = u_dense_matches.reshape((-1, 4))
u_dense_certainty = u_dense_certainty.reshape((-1,))
mkpts1 = u_dense_matches[:, :2]
mkpts2 = u_dense_matches[:, 2:]
w1, h1 = shapes1[b, :]
w2, h2 = shapes2[b, :]
mkpts1[:, 0] = ((mkpts1[:, 0] + 1)/2) * w1
mkpts1[:, 1] = ((mkpts1[:, 1] + 1)/2) * h1
mkpts2[:, 0] = ((mkpts2[:, 0] + 1)/2) * w2
mkpts2[:, 1] = ((mkpts2[:, 1] + 1)/2) * h2
mkpts1 = mkpts1.cpu().detach().numpy()
mkpts2 = mkpts2.cpu().detach().numpy()
mconf = u_dense_certainty.sqrt().cpu().detach().numpy()
if mconf.shape[0] > min_matches:
try:
# calc Fundamental matrix from keypoints
F, inliers = cv2.findFundamentalMat(mkpts1, mkpts2, cv2.USAC_MAGSAC, 0.200, 0.999, 2000)
inliers = inliers > 0
mkpts1 = mkpts1[inliers[:,0]]
mkpts2 = mkpts2[inliers[:,0]]
mconf = mconf[inliers[:,0]]
except:
pass
store_mkpts1.append(mkpts1)
store_mkpts2.append(mkpts2)
store_mconf.append(mconf)
return store_mkpts1, store_mkpts2, store_mconf
def detect_dkm(
img_fnames, index_pairs, feature_dir, device, file_keypoints,
resize_to=(540, 720),
detection_threshold=0.4,
num_features=2000,
min_matches=15
):
t=time()
dkm_model = DKMv3_outdoor(device=device)
dkm_model.upsample_preds=False
fnames1, fnames2 = [], []
for pair_idx in progress_bar(index_pairs):
idx1, idx2 = pair_idx
fname1, fname2 = img_fnames[idx1], img_fnames[idx2]
fnames1.append(fname1)
fnames2.append(fname2)
cnt_pairs = 0
with h5py.File(file_keypoints, mode='w') as f_match:
dataloader = get_dkm_dataloader(fnames1, fnames2, resize_to, device, batch_size=4)
for X in tqdm(dataloader):
images1, images2, idxs, shapes1, shapes2 = X
store_mkpts1, store_mkpts2, store_mconf = get_dkm_mkpts(
dkm_model, images1.to(device), images2.to(device), shapes1, shapes2,
detection_threshold=detection_threshold, num_features = num_features, min_matches=min_matches,
)
for b in range(images1.shape[0]):
mkpts1 = store_mkpts1[b]
mkpts2 = store_mkpts2[b]
mconf = store_mconf[b]
file1 = fnames1[idxs[b]]
file2 = fnames2[idxs[b]]
key1, key2 = file1.split('/')[-1], file2.split('/')[-1]
n_matches = mconf.shape[0]
group = f_match.require_group(key1)
if n_matches >= min_matches:
group.create_dataset(key2, data=np.concatenate([mkpts1, mkpts2], axis=1).astype(np.float32))
cnt_pairs+=1
print (f'{key1}-{key2}: {n_matches} matches @ {cnt_pairs}th pair(dkm)')
else:
print (f'{key1}-{key2}: {n_matches} matches --> skipped')
gc.collect()
t=time() -t
print(f'Features matched in {t:.4f} sec')
return t
def get_unique_idxs(A, dim=0):
# https://stackoverflow.com/questions/72001505/how-to-get-unique-elements-and-their-firstly-appeared-indices-of-a-pytorch-tenso
unique, idx, counts = torch.unique(A, dim=dim, sorted=True, return_inverse=True, return_counts=True)
_, ind_sorted = torch.sort(idx, stable=True)
cum_sum = counts.cumsum(0)
cum_sum = torch.cat((torch.tensor([0],device=cum_sum.device), cum_sum[:-1]))
first_indices = ind_sorted[cum_sum]
return first_indices
def get_keypoint_from_h5(fp, key1, key2):
rc = -1
try:
kpts = np.array(fp[key1][key2])
rc = 0
return (rc, kpts)
except:
return (rc, None)
def get_keypoint_from_multi_h5(fps, key1, key2):
list_mkpts = []
for fp in fps:
rc, mkpts = get_keypoint_from_h5(fp, key1, key2)
if rc == 0:
list_mkpts.append(mkpts)
if len(list_mkpts) > 0:
list_mkpts = np.concatenate(list_mkpts, axis=0)
else:
list_mkpts = None
return list_mkpts
def keypoints_merger(
img_fnames,
index_pairs,
files_keypoints,
feature_dir = 'featureout',
):
# open h5 files
fps = [ h5py.File(file, mode="r") for file in files_keypoints ]
# temprary file
with h5py.File(f'{feature_dir}/merge_tmp.h5', mode='w') as f_match:
counter = 0
for pair_idx in progress_bar(index_pairs):
idx1, idx2 = pair_idx
fname1, fname2 = img_fnames[idx1], img_fnames[idx2]
key1, key2 = fname1.split('/')[-1], fname2.split('/')[-1]
# extract keypoints
mkpts = get_keypoint_from_multi_h5(fps, key1, key2)
if mkpts is None:
print(f"skipped key1={key1}, key2={key2}")
continue
print (f'{key1}-{key2}: {mkpts.shape[0]} matches')
# regist tmp file
group = f_match.require_group(key1)
group.create_dataset(key2, data=mkpts)
counter += 1
print( f"Ensembled pairs : {counter} pairs" )
for fp in fps:
fp.close()
# Let's find unique loftr pixels and group them together.
kpts = defaultdict(list)
match_indexes = defaultdict(dict)
total_kpts=defaultdict(int)
with h5py.File(f'{feature_dir}/merge_tmp.h5', mode='r') as f_match:
for k1 in f_match.keys():
group = f_match[k1]
for k2 in group.keys():
matches = group[k2][...]
total_kpts[k1]
kpts[k1].append(matches[:, :2])
kpts[k2].append(matches[:, 2:])
current_match = torch.arange(len(matches)).reshape(-1, 1).repeat(1, 2)
current_match[:, 0]+=total_kpts[k1]
current_match[:, 1]+=total_kpts[k2]
total_kpts[k1]+=len(matches)
total_kpts[k2]+=len(matches)
match_indexes[k1][k2]=current_match
for k in kpts.keys():
kpts[k] = np.round(np.concatenate(kpts[k], axis=0))
unique_kpts = {}
unique_match_idxs = {}
out_match = defaultdict(dict)
for k in kpts.keys():
uniq_kps, uniq_reverse_idxs = torch.unique(torch.from_numpy(kpts[k]),dim=0, return_inverse=True)
unique_match_idxs[k] = uniq_reverse_idxs
unique_kpts[k] = uniq_kps.numpy()
for k1, group in match_indexes.items():
for k2, m in group.items():
m2 = deepcopy(m)
m2[:,0] = unique_match_idxs[k1][m2[:,0]]
m2[:,1] = unique_match_idxs[k2][m2[:,1]]
mkpts = np.concatenate([unique_kpts[k1][ m2[:,0]],
unique_kpts[k2][ m2[:,1]],
],
axis=1)
unique_idxs_current = get_unique_idxs(torch.from_numpy(mkpts), dim=0)
m2_semiclean = m2[unique_idxs_current]
unique_idxs_current1 = get_unique_idxs(m2_semiclean[:, 0], dim=0)
m2_semiclean = m2_semiclean[unique_idxs_current1]
unique_idxs_current2 = get_unique_idxs(m2_semiclean[:, 1], dim=0)
m2_semiclean2 = m2_semiclean[unique_idxs_current2]
out_match[k1][k2] = m2_semiclean2.numpy()
with h5py.File(f'{feature_dir}/keypoints.h5', mode='w') as f_kp:
for k, kpts1 in unique_kpts.items():
f_kp[k] = kpts1
with h5py.File(f'{feature_dir}/matches.h5', mode='w') as f_match:
for k1, gr in out_match.items():
group = f_match.require_group(k1)
for k2, match in gr.items():
group[k2] = match
return
def arr_to_str(a):
return ';'.join([str(x) for x in a.reshape(-1)])
# Function to create a submission file.
def create_submission(out_results, data_dict):
with open(f'submission.csv', 'w') as f:
f.write('image_path,dataset,scene,rotation_matrix,translation_vector\n')
for dataset in data_dict:
if dataset in out_results:
res = out_results[dataset]
else:
res = {}
for scene in data_dict[dataset]:
if scene in res:
scene_res = res[scene]
else:
scene_res = {"R":{}, "t":{}}
for image in data_dict[dataset][scene]:
if image in scene_res:
print (image)
R = scene_res[image]['R'].reshape(-1)
T = scene_res[image]['t'].reshape(-1)
else:
R = np.eye(3).reshape(-1)
T = np.zeros((3))
f.write(f'{image},{dataset},{scene},{arr_to_str(R)},{arr_to_str(T)}\n')
src = '/kaggle/input/image-matching-challenge-2024'
DEBUG = False
DUMP = False
# Get data from csv.
data_dict = {}
with open(f'{src}/sample_submission.csv', 'r') as f:
for i, l in enumerate(f):
# Skip header.
if l and i > 0:
image, dataset, scene, _, _ = l.strip().split(',')
if dataset not in data_dict:
data_dict[dataset] = {}
if scene not in data_dict[dataset]:
data_dict[dataset][scene] = []
data_dict[dataset][scene].append(image)
#if len(data_dict[dataset][scene]) == 21:
# break
for dataset in data_dict:
for scene in data_dict[dataset]:
print(f'{dataset} / {scene} -> {len(data_dict[dataset][scene])} images')
church / church -> 41 images
out_results = {}
timings = {"shortlisting":[],
"feature_detection": [],
"feature_matching":[],
"RANSAC": [],
"Reconstruction": []}
gc.collect()
datasets = []
for dataset in data_dict:
datasets.append(dataset)
for dataset in datasets:
print(dataset)
if dataset not in out_results:
out_results[dataset] = {}
for scene in data_dict[dataset]:
print(scene)
# Fail gently if the notebook has not been submitted and the test data is not populated.
# You may want to run this on the training data in that case?
img_dir = f'{src}/test/{dataset}/images'
if not os.path.exists(img_dir):
continue
# Wrap the meaty part in a try-except block.
try:
out_results[dataset][scene] = {}
img_fnames = [f'{src}/{x}' for x in data_dict[dataset][scene]]
print (f"Got {len(img_fnames)} images")
feature_dir = f'featureout/{dataset}_{scene}'
if not os.path.isdir(feature_dir):
os.makedirs(feature_dir, exist_ok=True)
#############################################################
# get image pairs
#############################################################
t=time()
index_pairs = get_image_pairs_shortlist(img_fnames,
sim_th = 1.0, # should be strict
min_pairs = 50, # we select at least min_pairs PER IMAGE with biggest similarity
exhaustive_if_less = 50,
device=device)
t=time() -t
timings['shortlisting'].append(t)
print (f'{len(index_pairs)}, pairs to match, {t:.4f} sec')
gc.collect()
#############################################################
# get keypoints
#############################################################
files_keypoints = []
if CONFIG.use_superglue:
resize_to = CONFIG.params_sg["resize_to"]
file_keypoints = f"{feature_dir}/matches_superglue_{resize_to}pix.h5"
!rm -rf {file_keypoints}
t = detect_superglue(
img_fnames, index_pairs, feature_dir, device,
CONFIG.params_sg["sg_config"], file_keypoints,
resize_to=CONFIG.params_sg["resize_to"],
min_matches=CONFIG.params_sg["min_matches"],
)
gc.collect()
files_keypoints.append( file_keypoints )
timings['feature_matching'].append(t)
if CONFIG.use_aliked_lightglue:
model_name = "aliked"
file_keypoints = f'{feature_dir}/matches_lightglue_{model_name}.h5'
t = detect_lightglue_common(
img_fnames, model_name, index_pairs, feature_dir, device, file_keypoints,
resize_to=CONFIG.params_aliked_lightglue["resize_to"],
detection_threshold=CONFIG.params_aliked_lightglue["detection_threshold"],
num_features=CONFIG.params_aliked_lightglue["num_features"],
min_matches=CONFIG.params_aliked_lightglue["min_matches"],
)
gc.collect()
files_keypoints.append(file_keypoints)
timings['feature_matching'].append(t)
if CONFIG.use_doghardnet_lightglue:
model_name = "doghardnet"
file_keypoints = f'{feature_dir}/matches_lightglue_{model_name}.h5'
t = detect_lightglue_common(
img_fnames, model_name, index_pairs, feature_dir, device, file_keypoints,
resize_to=CONFIG.params_doghardnet_lightglue["resize_to"],
detection_threshold=CONFIG.params_doghardnet_lightglue["detection_threshold"],
num_features=CONFIG.params_doghardnet_lightglue["num_features"],
min_matches=CONFIG.params_doghardnet_lightglue["min_matches"],
)
gc.collect()
files_keypoints.append(file_keypoints)
timings['feature_matching'].append(t)
if CONFIG.use_superpoint_lightglue:
model_name = "superpoint"
file_keypoints = f'{feature_dir}/matches_lightglue_{model_name}.h5'
t = detect_lightglue_common(
img_fnames, model_name, index_pairs, feature_dir, device, file_keypoints,
resize_to=CONFIG.params_superpoint_lightglue["resize_to"],
detection_threshold=CONFIG.params_superpoint_lightglue["detection_threshold"],
num_features=CONFIG.params_superpoint_lightglue["num_features"],
min_matches=CONFIG.params_superpoint_lightglue["min_matches"],
)
gc.collect()
files_keypoints.append(file_keypoints)
timings['feature_matching'].append(t)
if CONFIG.use_loftr:
file_keypoints = f'{feature_dir}/matches_loftr_{CONFIG.params_loftr["resize_small_edge_to"]}pix.h5'
t = detect_loftr(
img_fnames, index_pairs, feature_dir, device, file_keypoints,
resize_small_edge_to=CONFIG.params_loftr["resize_small_edge_to"],
min_matches=CONFIG.params_loftr["min_matches"],
)
gc.collect()
files_keypoints.append( file_keypoints )
timings['feature_matching'].append(t)
if CONFIG.use_dkm:
file_keypoints = f'{feature_dir}/matches_dkm.h5'
t = detect_dkm(
img_fnames, index_pairs, feature_dir, device, file_keypoints,
resize_to=CONFIG.params_dkm["resize_to"],
detection_threshold=CONFIG.params_dkm["detection_threshold"],
num_features=CONFIG.params_dkm["num_features"],
min_matches=CONFIG.params_dkm["min_matches"]
)
gc.collect()
files_keypoints.append(file_keypoints)
timings['feature_matching'].append(t)
#############################################################
# merge keypoints
#############################################################
keypoints_merger(
img_fnames,
index_pairs,
files_keypoints,
feature_dir = feature_dir,
)
#############################################################
# regist keypoints from h5 into colmap db
#############################################################
database_path = f'{feature_dir}/colmap.db'
if os.path.isfile(database_path):
os.remove(database_path)
gc.collect()
import_into_colmap(img_dir, feature_dir=feature_dir,database_path=database_path)
output_path = f'{feature_dir}/colmap_rec'
#############################################################
# Calculate fundamental matrix with colmap api
#############################################################
t=time()
options = pycolmap.SiftMatchingOptions()
options.confidence = 0.9999
options.max_num_trials = 20000
pycolmap.match_exhaustive(database_path, sift_options=options)
t=time() - t
timings['RANSAC'].append(t)
print(f'RANSAC in {t:.4f} sec')
#############################################################
# Execute bundle adjustmnet with colmap api
# --> Bundle adjustment Calcs Camera matrix, R and t
#############################################################
t=time()
# By default colmap does not generate a reconstruction if less than 10 images are registered. Lower it to 3.
mapper_options = pycolmap.IncrementalMapperOptions()
mapper_options.min_model_size = 3
os.makedirs(output_path, exist_ok=True)
maps = pycolmap.incremental_mapping(database_path=database_path, image_path=img_dir, output_path=output_path, options=mapper_options)
print(maps)
clear_output(wait=False)
t=time() - t
timings['Reconstruction'].append(t)
print(f'Reconstruction done in {t:.4f} sec')
#############################################################
# Extract R,t from maps
#############################################################
imgs_registered = 0
best_idx = None
list_num_images = []
print ("Looking for the best reconstruction")
if isinstance(maps, dict):
for idx1, rec in maps.items():
print (idx1, rec.summary())
list_num_images.append( len(rec.images) )
if len(rec.images) > imgs_registered:
imgs_registered = len(rec.images)
best_idx = idx1
list_num_images = np.array(list_num_images)
print(f"list_num_images = {list_num_images}")
if best_idx is not None:
print (maps[best_idx].summary())
for k, im in maps[best_idx].images.items():
key1 = f'test/{dataset}/images/{im.name}'
out_results[dataset][scene][key1] = {}
out_results[dataset][scene][key1]["R"] = deepcopy(im.rotmat())
out_results[dataset][scene][key1]["t"] = deepcopy(np.array(im.tvec))
print(f'Registered: {dataset} / {scene} -> {len(out_results[dataset][scene])} images')
print(f'Total: {dataset} / {scene} -> {len(data_dict[dataset][scene])} images')
create_submission(out_results, data_dict)
gc.collect()
except:
pass
Reconstruction done in 331.3675 sec Looking for the best reconstruction 0 Reconstruction: num_reg_images = 40 num_cameras = 40 num_points3D = 29091 num_observations = 164313 mean_track_length = 5.64824 mean_observations_per_image = 4107.82 mean_reprojection_error = 0.963606 list_num_images = [40] Reconstruction: num_reg_images = 40 num_cameras = 40 num_points3D = 29091 num_observations = 164313 mean_track_length = 5.64824 mean_observations_per_image = 4107.82 mean_reprojection_error = 0.963606 Registered: church / church -> 40 images Total: church / church -> 41 images test/church/images/00046.png test/church/images/00090.png test/church/images/00092.png test/church/images/00087.png test/church/images/00050.png test/church/images/00068.png test/church/images/00083.png test/church/images/00096.png test/church/images/00069.png test/church/images/00081.png test/church/images/00042.png test/church/images/00018.png test/church/images/00030.png test/church/images/00024.png test/church/images/00032.png test/church/images/00026.png test/church/images/00037.png test/church/images/00008.png test/church/images/00035.png test/church/images/00021.png test/church/images/00010.png test/church/images/00039.png test/church/images/00011.png test/church/images/00013.png test/church/images/00006.png test/church/images/00012.png test/church/images/00029.png test/church/images/00001.png test/church/images/00098.png test/church/images/00072.png test/church/images/00066.png test/church/images/00058.png test/church/images/00059.png test/church/images/00111.png test/church/images/00061.png test/church/images/00060.png test/church/images/00074.png test/church/images/00102.png test/church/images/00076.png test/church/images/00063.png
!cat submission.csv
image_path,dataset,scene,rotation_matrix,translation_vector test/church/images/00046.png,church,church,-0.19045505245638727;-0.3205655736895829;-0.9278817736968995;0.3810124792406934;0.8469522530229538;-0.3708117740339467;0.9047410478455676;-0.4241575109135888;-0.03916685178685886,5.4489788089748705;4.733225577664254;10.512054854114277 test/church/images/00090.png,church,church,0.9995787801824203;-0.0024292498045050577;-0.028919905850676064;-0.00236521167013138;0.9863569513383538;-0.1646036765089527;0.02892521361684525;0.16460274387717777;0.9859357325532553,-0.01124905218176241;0.022314701446219835;-0.5090553736532376 test/church/images/00092.png,church,church,0.9474947439618744;-0.11968791349365804;-0.2965274245801842;0.1437975977599753;0.9877379746628613;0.06079427840980734;0.2856150574516031;-0.10024219058083006;0.9530873738458998,0.08742134929926888;-0.059116058263106515;-0.501689606321542 test/church/images/00087.png,church,church,0.8923582292613469;-0.15707227252578382;-0.4231135685287573;0.18365597898498331;0.9827326728825436;0.022516106062083194;0.412270872185534;-0.0977997691854;0.9057968497928559,0.776907744820194;-0.017032735640488773;-0.5614159015006596 test/church/images/00050.png,church,church,0.923066557363316;0.17227239672352865;0.343904568164727;-0.20180558337651247;0.9780589675871709;0.05172197251055717;-0.3274486787200425;-0.11714468510969038;0.9375790556294705,-2.8187925847691986;0.4350366265410224;0.996373302305946 test/church/images/00068.png,church,church,0.5750216444127919;-0.2057560163985181;-0.7918425160173086;0.5254450568784128;0.8347411036693562;0.16466566748082856;0.6271025439741498;-0.5107560587606161;0.5881076838304969,4.618447806045479;0.41551386307216553;0.6780916886668167 test/church/images/00083.png,church,church,0.9350598692932851;-0.1418081133598155;-0.32488998110522616;0.17671136238701293;0.9809722713520954;0.0804145337711824;0.30730462938353253;-0.13260415462435768;0.9423269087401723,1.401318948731229;-0.039184509409423716;-0.5373441894441385 test/church/images/00096.png,church,church,0.9432617456998709;0.08764688046173384;0.32027379450176496;-0.012517512560910146;0.9732348836071537;-0.2294715084917024;-0.3318140909884927;0.21244266444265975;0.9191123562143988,-0.9779618710924005;0.0031846118198715296;-0.9447097838212319 test/church/images/00069.png,church,church,0.5874407755809463;-0.20225814997283464;-0.7835846960950823;0.34713440575753696;0.9376384075385067;0.018218700502916685;0.7310342259595086;-0.2827116153931769;0.6210177960393426,4.647343668529042;0.20291130261573695;0.7878911028475933 test/church/images/00081.png,church,church,0.8898344548709913;-0.1293904859141845;-0.43755313400698775;0.16108871391860252;0.9862852670107054;0.03594159603679402;0.42690170902972074;-0.10246694214483358;0.8984739598869802,1.4512251768099498;0.022205189057821026;-0.28184928393565006 test/church/images/00042.png,church,church,0.7257352096283635;0.19482885694072782;0.6598106713359861;-0.2522028140055299;0.9676386060303515;-0.008322783628714442;-0.6400797966764515;-0.16036597090033694;0.7513857925618669,-4.737008904827578;2.552398919778121;6.942950586544505 test/church/images/00018.png,church,church,0.9846400584465941;-0.058868736137131755;-0.16437282989565105;0.08297938069748165;0.9861064503311586;0.14390445091915852;0.15361763466916906;-0.15533364259199886;0.9758448041557382,0.22504822565620958;1.2253019590648935;2.895676780419565 test/church/images/00030.png,church,church,0.9461184830777511;0.07337675344797576;0.31539763479152794;-0.15586641810425836;0.9569288050081982;0.24493452564497778;-0.28384058146432056;-0.2808969814701054;0.9168050011398169,-2.7495653305029197;0.24870714085630966;0.77309550933949 test/church/images/00024.png,church,church,0.8116517566335592;0.2093626190919364;0.5453335856891839;-0.2284651045849356;0.9729754424003588;-0.03350349941156062;-0.5376105671772571;-0.09739652053739273;0.837549279652581,0.01656199335221601;0.5600915352511748;2.0590611904068665 test/church/images/00032.png,church,church,0.974368683566628;0.08190772597735088;0.20951561495472268;-0.1048823050371904;0.9893434958210527;0.10099083803328043;-0.19901098104055;-0.12037679054451375;0.972575990719224,-1.0397352240973365;0.5630160710685964;1.6466550646027849 test/church/images/00026.png,church,church,0.9645595065717133;0.13794593203481662;0.22493527539534278;-0.1836096707501054;0.9631187334262391;0.19669721434345472;-0.18950579698038486;-0.23102645986657638;0.9543135374458157,-2.0960475995847574;0.6241056188291473;1.0916923035203818 test/church/images/00037.png,church,church,-0.7833926937623681;0.19383919034274505;0.590527099841328;-0.08524557184166648;0.9076339827554167;-0.41101550558212185;-0.6156533763743427;-0.37232636441005496;-0.6945098980749398,-0.5498955513208535;0.39540352642523857;3.445370393280146 test/church/images/00008.png,church,church,0.9999663360810358;-3.671735215840853e-05;0.008205203014252484;3.257310880761173e-05;0.9999998718518661;0.0005052081193885731;-0.008205220512675462;-0.0005049238431328101;0.9999662091332142,-0.8165602834576889;1.40543727750834;5.236422421239763 test/church/images/00035.png,church,church,-0.46598581912900716;0.20839141048401089;0.859901294572321;-0.3476321634129135;0.8505917821827548;-0.394519326578588;-0.8136394135804426;-0.4827697589116883;-0.3239201515016079,-0.5873677336060524;1.1614687929503975;3.3914736523908604 test/church/images/00021.png,church,church,0.9701923961118828;0.135210790060731;0.20110881824981344;-0.15090852449855452;0.9864203691862958;0.06481876648040251;-0.18961363811802343;-0.09323570939036561;0.9774220023787691,-1.7729476122270293;0.7843715128413354;2.0506093348389154 test/church/images/00010.png,church,church,0.9972112072516602;-0.01832211034328201;-0.07234713818980666;0.0135552952386382;0.997738259709579;-0.06583782410358144;0.07338979563045352;0.06467352923932151;0.9952041361012578,-0.5734624092951723;0.9745563778467372;4.923625934742633 test/church/images/00039.png,church,church,0.5831532835806126;0.24359678883725197;0.7749792592820333;-0.33556421521695085;0.9410223708132004;-0.043284582647120234;-0.7398168052396361;-0.23481376045130478;0.6305026507380723,-1.829182753366879;0.7865097238012043;2.0321684566132165 test/church/images/00011.png,church,church,0.9994551117785063;0.012242638142110896;0.030652852251213263;-0.012596838515874561;0.9998557960322672;0.011388889326281213;-0.0305090019374341;-0.011768812684490922;0.9994652049215015,0.28341244360331325;1.1662618882249485;4.263132829032265 test/church/images/00013.png,church,church,0.9940336264060289;0.01964442266883493;0.10729047596169707;-0.007513566524384699;0.993643729690009;-0.1123195653740338;-0.1088149617109843;0.11084329075652875;0.9878628796559271,0.6255139958988624;0.6256048301002536;4.357217437117637 test/church/images/00006.png,church,church,0.9708504514702527;-0.07323870466337691;0.22822246387951634;0.08839036236110172;0.9944606830322252;-0.056877884496113744;-0.2227926047284326;0.0753925861229788;0.9719461987353053,0.21229212682102747;1.0498449266991632;5.369542790522304 test/church/images/00012.png,church,church,0.9991012811326345;0.007436090407237434;-0.04172930143896225;-0.00973617324526048;0.9984284283258488;-0.0551894957515806;0.04125332677090006;0.0555461796186817;0.9976035209240706,0.1924046884539358;0.5375674160432287;2.849938089625637 test/church/images/00029.png,church,church,0.9979896123192642;-0.006893506521869631;0.06300169260168842;0.005725737604948875;0.9998087950969622;0.018697303967150834;-0.06311853635600223;-0.018298983977087385;0.9978382622217301,-0.9528981159312442;0.440762069959487;1.9988132085507897 test/church/images/00001.png,church,church,0.9463787001104561;0.10436304102719092;0.30573797874127506;-0.12205650793517793;0.9917454846271424;0.03928234453726395;-0.299114634961102;-0.07449328418878662;0.951304990927124,1.2873380829775245;1.6632030111866922;5.103387733384806 test/church/images/00098.png,church,church,0.9038236995107594;-0.13240994329670666;-0.4069033387904891;0.19778793206815684;0.9725141135631343;0.12286672799709969;0.3794504633425644;-0.1915304305761203;0.9051703928169763,1.9477375218833204;0.0910709111924087;-2.3442233609059167 test/church/images/00072.png,church,church,0.7487643290140215;-0.1762424581745275;-0.6389761932441476;0.32779061044740293;0.936330315949211;0.12585251343127685;0.5761122245697459;-0.3036842692189549;0.7588613637089768,3.26361746312355;0.3792559743742919;0.511732945298637 test/church/images/00066.png,church,church,0.5442941323428014;-0.23222972878851966;-0.8061099494262601;0.34880983607972094;0.9365659301217268;-0.03429222636783558;0.7629387889937406;-0.2625140217246219;0.5907713539489956,4.61155466425232;0.3739192917697607;0.9662541634298554 test/church/images/00104.png,church,church,1.0;0.0;0.0;0.0;1.0;0.0;0.0;0.0;1.0,0.0;0.0;0.0 test/church/images/00058.png,church,church,0.9066177453298658;0.2624931349378865;0.33036588498734426;0.0418004065316796;0.7232215745772292;-0.6893498966996511;-0.41987735098094653;0.6387862773858827;0.6447131935649442,0.5072940621740217;-0.47331259997475866;0.5547865777888344 test/church/images/00059.png,church,church,-0.1436521234773558;-0.26745417054384335;-0.9528023583509605;0.570081739311648;0.7646283301453589;-0.30058331165007535;0.8089319365211475;-0.5863546566693445;0.04263025543346266,5.810319328034658;2.604848300412115;4.068512011759298 test/church/images/00111.png,church,church,0.8642097406340425;0.18053174892195056;0.46962731162531707;-0.26954798658849854;0.9542828496568607;0.12918252891497362;-0.4248357413393175;-0.23822789610363265;0.8733625034306828,-3.536845878846759;0.9764794128668803;1.4898229049307894 test/church/images/00061.png,church,church,0.08378663348420157;0.31069077230048553;0.9468109864469847;-0.42490358537712675;0.870583313047108;-0.24807587181663598;-0.9013527296142432;-0.38151794066760364;0.2049568680618047,-5.493133205891144;2.2413042189542396;4.410691435946078 test/church/images/00060.png,church,church,0.9783354004235938;-0.049097470084256314;-0.20112007037919397;0.039475236719436854;0.9978883751901195;-0.051579999480105596;0.20322782773023682;0.04252327705796939;0.9782076573735765,0.38278234329129657;0.4712520337543369;1.00560341172369 test/church/images/00074.png,church,church,0.8547530873297045;-0.163773701014919;-0.4925193748028748;0.15668625942170772;0.9860616360786036;-0.05596307677764591;0.49481974072259083;-0.02933640587841841;0.8685003163392427,3.3742017847774814;0.12922666611352002;0.2531314341511051 test/church/images/00102.png,church,church,0.9374558054716234;-0.06774781858399104;-0.3414481598495743;0.021835397234206418;0.9903915353740851;-0.13655702869858702;0.3474188080907166;0.12056052312624123;0.9299275950569232,-0.437281090399856;0.5275633621010443;2.480261933989846 test/church/images/00076.png,church,church,0.46404609326404866;-0.24221723130857326;-0.8520516628606407;0.31010442239641073;0.945441710580714;-0.09987601866515208;0.8297568743533026;-0.21787791249096924;0.5138411668132626,4.404216774065016;1.1504250101446147;3.1897923353882205 test/church/images/00063.png,church,church,0.8835200606906524;-0.12094085246348368;-0.4525103452547708;0.39336657335510855;0.7160418789154879;0.5766686800980594;0.25427355608487096;-0.6875007911592421;0.6802114530286668,0.1696906735204117;0.2717531921150028;-0.06247921888739553
pd.read_csv('submission.csv').rotation_matrix.value_counts()
rotation_matrix -0.19045505245638727;-0.3205655736895829;-0.9278817736968995;0.3810124792406934;0.8469522530229538;-0.3708117740339467;0.9047410478455676;-0.4241575109135888;-0.03916685178685886 1 0.9995787801824203;-0.0024292498045050577;-0.028919905850676064;-0.00236521167013138;0.9863569513383538;-0.1646036765089527;0.02892521361684525;0.16460274387717777;0.9859357325532553 1 0.9474947439618744;-0.11968791349365804;-0.2965274245801842;0.1437975977599753;0.9877379746628613;0.06079427840980734;0.2856150574516031;-0.10024219058083006;0.9530873738458998 1 0.8923582292613469;-0.15707227252578382;-0.4231135685287573;0.18365597898498331;0.9827326728825436;0.022516106062083194;0.412270872185534;-0.0977997691854;0.9057968497928559 1 0.923066557363316;0.17227239672352865;0.343904568164727;-0.20180558337651247;0.9780589675871709;0.05172197251055717;-0.3274486787200425;-0.11714468510969038;0.9375790556294705 1 0.5750216444127919;-0.2057560163985181;-0.7918425160173086;0.5254450568784128;0.8347411036693562;0.16466566748082856;0.6271025439741498;-0.5107560587606161;0.5881076838304969 1 0.9350598692932851;-0.1418081133598155;-0.32488998110522616;0.17671136238701293;0.9809722713520954;0.0804145337711824;0.30730462938353253;-0.13260415462435768;0.9423269087401723 1 0.9432617456998709;0.08764688046173384;0.32027379450176496;-0.012517512560910146;0.9732348836071537;-0.2294715084917024;-0.3318140909884927;0.21244266444265975;0.9191123562143988 1 0.5874407755809463;-0.20225814997283464;-0.7835846960950823;0.34713440575753696;0.9376384075385067;0.018218700502916685;0.7310342259595086;-0.2827116153931769;0.6210177960393426 1 0.8898344548709913;-0.1293904859141845;-0.43755313400698775;0.16108871391860252;0.9862852670107054;0.03594159603679402;0.42690170902972074;-0.10246694214483358;0.8984739598869802 1 0.7257352096283635;0.19482885694072782;0.6598106713359861;-0.2522028140055299;0.9676386060303515;-0.008322783628714442;-0.6400797966764515;-0.16036597090033694;0.7513857925618669 1 0.9846400584465941;-0.058868736137131755;-0.16437282989565105;0.08297938069748165;0.9861064503311586;0.14390445091915852;0.15361763466916906;-0.15533364259199886;0.9758448041557382 1 0.9461184830777511;0.07337675344797576;0.31539763479152794;-0.15586641810425836;0.9569288050081982;0.24493452564497778;-0.28384058146432056;-0.2808969814701054;0.9168050011398169 1 0.8116517566335592;0.2093626190919364;0.5453335856891839;-0.2284651045849356;0.9729754424003588;-0.03350349941156062;-0.5376105671772571;-0.09739652053739273;0.837549279652581 1 0.974368683566628;0.08190772597735088;0.20951561495472268;-0.1048823050371904;0.9893434958210527;0.10099083803328043;-0.19901098104055;-0.12037679054451375;0.972575990719224 1 0.9645595065717133;0.13794593203481662;0.22493527539534278;-0.1836096707501054;0.9631187334262391;0.19669721434345472;-0.18950579698038486;-0.23102645986657638;0.9543135374458157 1 -0.7833926937623681;0.19383919034274505;0.590527099841328;-0.08524557184166648;0.9076339827554167;-0.41101550558212185;-0.6156533763743427;-0.37232636441005496;-0.6945098980749398 1 0.9999663360810358;-3.671735215840853e-05;0.008205203014252484;3.257310880761173e-05;0.9999998718518661;0.0005052081193885731;-0.008205220512675462;-0.0005049238431328101;0.9999662091332142 1 -0.46598581912900716;0.20839141048401089;0.859901294572321;-0.3476321634129135;0.8505917821827548;-0.394519326578588;-0.8136394135804426;-0.4827697589116883;-0.3239201515016079 1 0.9701923961118828;0.135210790060731;0.20110881824981344;-0.15090852449855452;0.9864203691862958;0.06481876648040251;-0.18961363811802343;-0.09323570939036561;0.9774220023787691 1 0.9972112072516602;-0.01832211034328201;-0.07234713818980666;0.0135552952386382;0.997738259709579;-0.06583782410358144;0.07338979563045352;0.06467352923932151;0.9952041361012578 1 0.5831532835806126;0.24359678883725197;0.7749792592820333;-0.33556421521695085;0.9410223708132004;-0.043284582647120234;-0.7398168052396361;-0.23481376045130478;0.6305026507380723 1 0.9994551117785063;0.012242638142110896;0.030652852251213263;-0.012596838515874561;0.9998557960322672;0.011388889326281213;-0.0305090019374341;-0.011768812684490922;0.9994652049215015 1 0.9940336264060289;0.01964442266883493;0.10729047596169707;-0.007513566524384699;0.993643729690009;-0.1123195653740338;-0.1088149617109843;0.11084329075652875;0.9878628796559271 1 0.9708504514702527;-0.07323870466337691;0.22822246387951634;0.08839036236110172;0.9944606830322252;-0.056877884496113744;-0.2227926047284326;0.0753925861229788;0.9719461987353053 1 0.9991012811326345;0.007436090407237434;-0.04172930143896225;-0.00973617324526048;0.9984284283258488;-0.0551894957515806;0.04125332677090006;0.0555461796186817;0.9976035209240706 1 0.9979896123192642;-0.006893506521869631;0.06300169260168842;0.005725737604948875;0.9998087950969622;0.018697303967150834;-0.06311853635600223;-0.018298983977087385;0.9978382622217301 1 0.9463787001104561;0.10436304102719092;0.30573797874127506;-0.12205650793517793;0.9917454846271424;0.03928234453726395;-0.299114634961102;-0.07449328418878662;0.951304990927124 1 0.9038236995107594;-0.13240994329670666;-0.4069033387904891;0.19778793206815684;0.9725141135631343;0.12286672799709969;0.3794504633425644;-0.1915304305761203;0.9051703928169763 1 0.7487643290140215;-0.1762424581745275;-0.6389761932441476;0.32779061044740293;0.936330315949211;0.12585251343127685;0.5761122245697459;-0.3036842692189549;0.7588613637089768 1 0.5442941323428014;-0.23222972878851966;-0.8061099494262601;0.34880983607972094;0.9365659301217268;-0.03429222636783558;0.7629387889937406;-0.2625140217246219;0.5907713539489956 1 1.0;0.0;0.0;0.0;1.0;0.0;0.0;0.0;1.0 1 0.9066177453298658;0.2624931349378865;0.33036588498734426;0.0418004065316796;0.7232215745772292;-0.6893498966996511;-0.41987735098094653;0.6387862773858827;0.6447131935649442 1 -0.1436521234773558;-0.26745417054384335;-0.9528023583509605;0.570081739311648;0.7646283301453589;-0.30058331165007535;0.8089319365211475;-0.5863546566693445;0.04263025543346266 1 0.8642097406340425;0.18053174892195056;0.46962731162531707;-0.26954798658849854;0.9542828496568607;0.12918252891497362;-0.4248357413393175;-0.23822789610363265;0.8733625034306828 1 0.08378663348420157;0.31069077230048553;0.9468109864469847;-0.42490358537712675;0.870583313047108;-0.24807587181663598;-0.9013527296142432;-0.38151794066760364;0.2049568680618047 1 0.9783354004235938;-0.049097470084256314;-0.20112007037919397;0.039475236719436854;0.9978883751901195;-0.051579999480105596;0.20322782773023682;0.04252327705796939;0.9782076573735765 1 0.8547530873297045;-0.163773701014919;-0.4925193748028748;0.15668625942170772;0.9860616360786036;-0.05596307677764591;0.49481974072259083;-0.02933640587841841;0.8685003163392427 1 0.9374558054716234;-0.06774781858399104;-0.3414481598495743;0.021835397234206418;0.9903915353740851;-0.13655702869858702;0.3474188080907166;0.12056052312624123;0.9299275950569232 1 0.46404609326404866;-0.24221723130857326;-0.8520516628606407;0.31010442239641073;0.945441710580714;-0.09987601866515208;0.8297568743533026;-0.21787791249096924;0.5138411668132626 1 0.8835200606906524;-0.12094085246348368;-0.4525103452547708;0.39336657335510855;0.7160418789154879;0.5766686800980594;0.25427355608487096;-0.6875007911592421;0.6802114530286668 1 Name: count, dtype: int64
12min in a run
Cell In[25], line 1 12min in a run ^ SyntaxError: invalid decimal literal