<jupyter_start><jupyter_text>metrics> Functions for evluation if the labeling is good/done<jupyter_code>%load_ext autoreload
%autoreload 2
#hide
from nbdev.showdoc import *
#export 
import torch
import kornia as K
import kornia.geometry as KG
import cv2
import numpy as np

def leave_one_out_F_validation(corrs: np.ndarray, error: str = 'symepi'):
    assert error in ['symepi', 'sampson']
    metric = KG.symmetrical_epipolar_distance if error == 'symepi' else KG.sampson_epipolar_distance
    num = len(corrs)
    out = {"min": None,
           "max": None,
           "mean": None,
           "all": [],
           "max_idx": None}
    if num < 9:
        return out
    errors = []
    for leave_idx in range(num):
        corrs_cur = np.concatenate([
            corrs[:leave_idx],
            corrs[leave_idx+1:]      
        ], axis=0)
        pt = torch.from_numpy(corrs[leave_idx].reshape(1, 1, 4))
        F, _ = cv2.findFundamentalMat(corrs_cur[:,:2],
                                      corrs_cur[:,2:], cv2.FM_8POINT)
        errors.append(metric(pt[:,:,:2],
                             pt[:,:,2:],
                             torch.from_numpy(F)[None], squared=False).item())
    errors = np.array(errors)
    out["all"] = errors
    out["min"] = errors.min()
    out["mean"] = errors.mean()
    out["max_idx"] = np.argmax(errors)
    out["max"] = errors[out["max_idx"]]
    return out

def leave_one_out_H_validation(corrs: np.ndarray, error: str = 'symtransfer'):
    assert error in ['symtransfer']
    metric = KG.symmetric_transfer_error
    num = len(corrs)
    out = {"min": None,
           "max": None,
           "mean": None,
           "all": [],
           "max_idx": None}
    if num < 5:
        return out
    errors = []
    for leave_idx in range(num):
        corrs_cur = np.concatenate([
            corrs[:leave_idx],
            corrs[leave_idx+1:]      
        ], axis=0)
        pt = torch.from_numpy(corrs[leave_idx].reshape(1, 1, 4))
        H, _ = cv2.findHomography(corrs_cur[:,:2],
                                  corrs_cur[:,2:], 0)
        errors.append(metric(pt[:,:,:2],
                             pt[:,:,2:],
                             torch.from_numpy(H)[None], squared=False).item())
    errors = np.array(errors)
    out["all"] = errors
    out["min"] = errors.min()
    out["mean"] = errors.mean()
    out["max_idx"] = np.argmax(errors)
    out["max"] = errors[out["max_idx"]]
    return out

def get_error_stat_string(errors):
    out =  (f'Errors: min = {errors["min"]:.2f}, max={errors["max"]:.2f}, at {errors["max_idx"]}, mean={errors["mean"]:.2f}')
    return out

def get_big_errors_string(errors, th=3.0):
    mask = errors['all'] > th
    out =  (f'Error > {th} px, idxs: {np.arange(len(errors["all"]))[mask]}')
    return out<jupyter_output><empty_output><jupyter_text>The function estimates the fundamental matrix or homography using (n-1) correspondences and returns error on that leaved-out correspondence (corr). The operation is repeated for every corr and the output is dict with those errors, together with min, mean, max and max_idx.I have got the idea from the [VSAC](https://arxiv.org/abs/2106.10240) paper<jupyter_code>import matplotlib.pyplot as plt
corrs = np.loadtxt('sample_project/petrzin/corrs.txt')
errors = leave_one_out_F_validation(corrs, 'symepi')
print (errors.keys())
print (f'Symmetrical epipolar distance, min error = {errors["min"]:.4f}, max_error={errors["max"]:.4f}, at {errors["max_idx"]}, mean_error={errors["mean"]:.4f}')
errors = leave_one_out_F_validation(corrs, 'sampson')
print (f'Sampson error, min error = {errors["min"]:.4f}, max_error={errors["max"]:.4f}, at {errors["max_idx"]}, mean_error={errors["mean"]:.4f}')
print (get_error_stat_string(errors))

print (get_big_errors_string(errors))
import matplotlib.pyplot as plt
corrs = np.loadtxt('sample_project/petrzin/corrs.txt')
errors = leave_one_out_H_validation(corrs)
print (errors.keys())
print (f'Symmetrical transfer distance, min error = {errors["min"]:.4f}, max_error={errors["max"]:.4f}, at {errors["max_idx"]}, mean_error={errors["mean"]:.4f}')
print (get_error_stat_string(errors))<jupyter_output>dict_keys(['min', 'max', 'mean', 'all', 'max_idx'])
Symmetrical transfer distance, min error = 0.8725, max_error=152.3928, at 16, mean_error=50.3009
Errors: min = 0.87, max=152.39, at 16, mean=50.30
{'fraction_non_alphanumeric': 0.15884641794460974, 'fraction_numerical': 0.019455252918287938, 'mean_word_length': 3.259259259259259, 'pattern_counts': {'":': 10, '<': 10, '<?xml version=': 0, '>': 11, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '2', 'dir': 'jupyter-structured-clean-dedup', 'id': '105292', 'license': 'Apache-2.0', 'n_tokens_mistral': 1570, 'n_tokens_neox': 1523, 'n_words': 325, 'path': 'metrics.ipynb', 'repo_name': 'ducha-aiki/correspondence_annotator', 'size': '4369'}