{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"accelerator":"GPU"},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Παράδειγμα submission — Image Matching Challenge 2025\n\nΔιαγωνισμός: https://www.kaggle.com/competitions/image-matching-challenge-2025\n\nBaseline pipeline: **ALIKED** (ανίχνευση χαρακτηριστικών) + **LightGlue** (αντιστοίχιση) +\n**DINOv2** (shortlisting ζευγαριών) και τέλος **pycolmap** για 3D ανακατασκευή (Structure-from-Motion).\n\n> Επίλεξε accelerator (GPU) από τη δεξιά μπάρα και **απενεργοποίησε το internet** όταν υποβάλλεις.","metadata":{}},{"cell_type":"code","source":"\n# Install offline\n!pip install --no-index /kaggle/input/imc2024-packages-lightglue-rerun-kornia/* --no-deps\n\n# Φάκελος για checkpoints\n!mkdir -p /root/.cache/torch/hub/checkpoints\n\n# Βαρη ALIKED \n!cp /kaggle/input/aliked/pytorch/aliked-n16/1/aliked-n16.pth /root/.cache/torch/hub/checkpoints/\n\n# Βάρη LightGlue \n!cp /kaggle/input/lightglue/pytorch/aliked/1/aliked_lightglue.pth /root/.cache/torch/hub/checkpoints/\n!cp /kaggle/input/lightglue/pytorch/aliked/1/aliked_lightglue.pth /root/.cache/torch/hub/checkpoints/aliked_lightglue_v0-1_arxiv-pth","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-01T00:02:36.914405Z","iopub.execute_input":"2026-07-01T00:02:36.914713Z","iopub.status.idle":"2026-07-01T00:02:44.094081Z","shell.execute_reply.started":"2026-07-01T00:02:36.914682Z","shell.execute_reply":"2026-07-01T00:02:44.093071Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Import\nimport os\nimport sys\nimport gc\nimport dataclasses\nfrom time import time, sleep\nfrom copy import deepcopy\nfrom collections import defaultdict\n\nimport numpy as np\nimport pandas as pd\nimport h5py\nfrom PIL import Image\nfrom tqdm import tqdm\nfrom IPython.display import clear_output\n\nimport cv2\nimport torch\nimport torch.nn.functional as F\nimport kornia as K\nimport kornia.feature as KF\n\n# LightGlue + ALIKED \nfrom lightglue import match_pair, ALIKED, LightGlue\nfrom lightglue.utils import load_image, rbd\n\n# DINOv2\nfrom transformers import AutoImageProcessor, AutoModel\n\n# COLMAP (Structure-from-Motion) \nimport pycolmap\nsys.path.append('/kaggle/input/imc25-utils')\nfrom database import *      # COLMAPDatabase κ.λπ.\nfrom h5_to_db import *      # add_keypoints / add_matches (h5 -> colmap db)\nimport metric               # επίσημη μετρική αξιολόγησης","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-01T00:02:44.095253Z","iopub.execute_input":"2026-07-01T00:02:44.095636Z","iopub.status.idle":"2026-07-01T00:03:04.614613Z","shell.execute_reply.started":"2026-07-01T00:02:44.09561Z","shell.execute_reply":"2026-07-01T00:03:04.613769Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# === Επιλογή συσκευής (GPU αν υπάρχει) ===\n# Διάλεξε accelerator (π.χ. GPU T4) από τη δεξιά μπάρα του Kaggle.\ndevice = K.utils.get_cuda_device_if_available(0)\nprint(f'Χρησιμοποιείται device: {device}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-01T00:03:04.61547Z","iopub.execute_input":"2026-07-01T00:03:04.615918Z","iopub.status.idle":"2026-07-01T00:03:04.881737Z","shell.execute_reply.started":"2026-07-01T00:03:04.615898Z","shell.execute_reply":"2026-07-01T00:03:04.880185Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# === Βοηθητικές: φόρτωση εικόνας, global descriptors & shortlisting ζευγαριών ===\n\ndef read_image_tensor(path, device=torch.device('cpu')):\n    # Φόρτωση εικόνας ως tensor RGB float32 με διαστασεις -> [1, C, H, W]\n    img = K.io.load_image(path, K.io.ImageLoadType.RGB32, device=device)[None, ...]\n    return img\n\n\ndef compute_global_descriptors(filenames, device=torch.device('cpu')):\n    # Ένα global descriptor ανά εικόνα με DINOv2. Χρησιμεύει για να βρούμε ποιες\n    # εικόνες μοιαζουν αρκετα, ώστε να μην τις συγκρίνουμε εξαντλητικά.\n    processor = AutoImageProcessor.from_pretrained('/kaggle/input/dinov2/pytorch/base/1')\n    model = AutoModel.from_pretrained('/kaggle/input/dinov2/pytorch/base/1').eval().to(device)\n\n    descriptors = []\n    for path in tqdm(filenames, total=len(filenames)):\n        img_tensor = read_image_tensor(path)\n        with torch.inference_mode():\n            inputs = processor(images=img_tensor, return_tensors='pt', do_rescale=False).to(device)\n            outputs = model(**inputs)\n            # Max-pooling στα patch tokens (αγνοούμε το [CLS] token, index 0) και\n            # L2 normalization -> \"MAC\"-τύπου global descriptor.\n            emb = F.normalize(outputs.last_hidden_state[:, 1:].max(dim=1)[0], dim=1, p=2)\n        descriptors.append(emb.detach().cpu())\n\n    return torch.cat(descriptors, dim=0)\n\n\ndef all_pairs_exhaustive(filenames):\n    # Όλα τα δυνατά ζευγάρια (i, j) με i < j -- για λίγες εικόνες.\n    pairs = []\n    n = len(filenames)\n    for i in range(n):\n        for j in range(i + 1, n):\n            pairs.append((i, j))\n    return pairs\n\n\ndef build_pair_shortlist(filenames,\n                         sim_th=0.5,            # κατώφλι L2 απόστασης descriptors. Εύρος ~0.2-0.8.\n                                                 #   Μικρότερο = πιο ΑΥΣΤΗΡΟ (λιγότερα ζευγάρια).\n                         min_pairs=20,           # ελάχιστα ζευγάρια ανά εικόνα. Εύρος ~10-50.\n                         exhaustive_if_less = 20,  # αν #εικόνες <= τιμή -> εξαντλητικά. Εύρος ~10-50.\n                         device=torch.device('cpu')):\n    n = len(filenames)\n\n    # Λίγες εικόνες: τα συγκρίνουμε όλα, χωρίς DINO.\n    if n <= exhaustive_if_less:\n        return all_pairs_exhaustive(filenames)\n\n    # Πίνακας L2 αποστάσεων μεταξύ όλων των global descriptors.\n    embeddings = compute_global_descriptors(filenames, device=device)\n    dist_matrix = torch.cdist(embeddings, embeddings, p=2).cpu().numpy()\n\n    within_threshold = dist_matrix <= sim_th   # ποια ζευγάρια θεωρούνται \"όμοια\"\n    all_idx = np.arange(n)\n    pair_list = []\n\n    for i in range(n - 1):\n        candidates = all_idx[within_threshold[i]]\n        # Αν δεν βρέθηκαν αρκετά \"όμοια\", κράτα τα min_pairs κοντινότερα.\n        if len(candidates) < min_pairs:\n            candidates = np.argsort(dist_matrix[i])[:min_pairs]\n        for j in candidates:\n            if i == j:\n                continue\n            pair_list.append(tuple(sorted((i, int(j)))))\n\n    # Αφαίρεση duplicates & ταξινόμηση.\n    return sorted(set(pair_list))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-01T00:03:04.882948Z","iopub.execute_input":"2026-07-01T00:03:04.883278Z","iopub.status.idle":"2026-07-01T00:03:04.909842Z","shell.execute_reply.started":"2026-07-01T00:03:04.883244Z","shell.execute_reply":"2026-07-01T00:03:04.908881Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# === Βοηθητικές: ανίχνευση (ALIKED), αντιστοίχιση (LightGlue) & εισαγωγή σε COLMAP ===\n\ndef extract_aliked_features(filenames,\n                            feature_dir='.featureout',\n                            num_features=4096,   # μέγιστα keypoints/εικόνα. Εύρος ~1024-8192.\n                                                 #   Περισσότερα = πιο πλούσιο αλλά πιο αργό/βαρύ.\n                            resize_to=1024,      # μέγεθος resize. Εύρος ~512-2048 (μεγαλύτερο = πιο ακριβές/αργό).\n                            device=torch.device('cpu')):\n    dtype = torch.float32  # το ALIKED έχει προβλήματα με float16\n\n    # detection_threshold: όριο \"δύναμης\" keypoint. Εύρος ~0.01-0.2 (μικρότερο = περισσότερα keypoints).\n    extractor = ALIKED(max_num_keypoints=num_features,\n                       detection_threshold=0.05,\n                       resize=resize_to).eval().to(device, dtype)\n\n    os.makedirs(feature_dir, exist_ok=True)\n\n    # keypoints & descriptors -> αρχεία .h5 (ένα dataset ανά εικόνα, κλειδί = όνομα αρχείου).\n    with h5py.File(f'{feature_dir}/keypoints.h5', mode='w') as kp_file, \\\n         h5py.File(f'{feature_dir}/descriptors.h5', mode='w') as desc_file:\n        for path in tqdm(filenames):\n            key = path.split('/')[-1]\n            with torch.inference_mode():\n                img_tensor = read_image_tensor(path, device=device).to(dtype)\n                feats = extractor.extract(img_tensor)\n                keypoints = feats['keypoints'].reshape(-1, 2).detach().cpu().numpy()\n                descriptors = feats['descriptors'].reshape(len(keypoints), -1).detach().cpu().numpy()\n                kp_file[key] = keypoints\n                desc_file[key] = descriptors\n\n\ndef match_pairs_lightglue(filenames,\n                          pairs_to_match,\n                          feature_dir='.featureout',\n                          device=torch.device('cpu'),\n                          min_matches=25,   # ελάχιστα matches για να κρατηθεί ένα ζευγάρι. Εύρος ~15-100.\n                          verbose=True):\n    # confidence=-1 -> απενεργοποίηση early-stopping (πιο αργό αλλά πιο \"πλήρες\").\n    # mp=True -> mixed precision όταν τρέχουμε σε GPU.\n    matcher = KF.LightGlueMatcher(\n        'aliked',\n        {'width_confidence': -1,\n         'depth_confidence': -1,\n         'mp': 'cuda' in str(device)}\n    ).eval().to(device)\n\n    with h5py.File(f'{feature_dir}/keypoints.h5', mode='r') as kp_file, \\\n         h5py.File(f'{feature_dir}/descriptors.h5', mode='r') as desc_file, \\\n         h5py.File(f'{feature_dir}/matches.h5', mode='w') as match_file:\n        for i, j in tqdm(pairs_to_match):\n            key1 = filenames[i].split('/')[-1]\n            key2 = filenames[j].split('/')[-1]\n\n            kp1 = torch.from_numpy(kp_file[key1][...]).to(device)\n            kp2 = torch.from_numpy(kp_file[key2][...]).to(device)\n            desc1 = torch.from_numpy(desc_file[key1][...]).to(device)\n            desc2 = torch.from_numpy(desc_file[key2][...]).to(device)\n\n            with torch.inference_mode():\n                _, match_idxs = matcher(\n                    desc1, desc2,\n                    KF.laf_from_center_scale_ori(kp1[None]),\n                    KF.laf_from_center_scale_ori(kp2[None]),\n                )\n\n            if len(match_idxs) == 0:\n                continue\n            if verbose:\n                print(f'{key1}-{key2}: {len(match_idxs)} matches')\n\n            # Κράτα το ζευγάρι μόνο αν έχει αρκετά matches.\n            group = match_file.require_group(key1)\n            if len(match_idxs) >= min_matches:\n                group.create_dataset(key2, data=match_idxs.detach().cpu().numpy().reshape(-1, 2))\n\n\ndef populate_colmap_database(image_dir, feature_dir='.featureout', database_path='colmap.db'):\n    # Φτιάχνει τη βάση COLMAP και περνάει μέσα keypoints + matches από τα .h5.\n    db = COLMAPDatabase.connect(database_path)\n    db.create_tables()\n    single_camera = False\n    fname_to_id = add_keypoints(db, feature_dir, image_dir, '', 'simple-pinhole', single_camera)\n    add_matches(db, feature_dir, fname_to_id)\n    db.commit()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-01T00:03:04.911912Z","iopub.execute_input":"2026-07-01T00:03:04.912215Z","iopub.status.idle":"2026-07-01T00:03:04.927919Z","shell.execute_reply.started":"2026-07-01T00:03:04.912193Z","shell.execute_reply":"2026-07-01T00:03:04.926891Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# === Φόρτωση πληροφοριών dataset ===\n\n@dataclasses.dataclass\nclass Prediction:\n    # Μία πρόβλεψη ανά εικόνα.\n    image_id: str | None              # μοναδικό id (μόνο στο κρυφό test set)\n    dataset: str\n    filename: str\n    cluster_index: int | None = None      # σε ποιο cluster/scene κατέληξε η εικόνα\n    rotation: np.ndarray | None = None    # 3x3 πίνακας περιστροφής (camera-from-world)\n    translation: np.ndarray | None = None # διάνυσμα μετατόπισης\n\n# is_train=True  -> training data (έχουμε ground truth)\n# is_train=False -> υποβολή διαγωνισμού (το test είναι κρυφό & διαφορετικό)\nis_train = False\n\ndata_dir = '/kaggle/input/image-matching-challenge-2025'\nworkdir = '/kaggle/working/result/'\nos.makedirs(workdir, exist_ok=True)\n\n# Διαλέγουμε το κατάλληλο CSV ανάλογα με train/test.\ncsv_path = os.path.join(data_dir, 'train_labels.csv' if is_train else 'sample_submission.csv')\n\n# Ομαδοποίηση εικόνων ανά dataset.\nsamples = defaultdict(list)\ncompetition_data = pd.read_csv(csv_path)\nfor _, row in competition_data.iterrows():\n    # Προσοχή: στο test, η στήλη \"scene\" και τα rotation/translation είναι τυχαία/άκυρα.\n    samples[row.dataset].append(\n        Prediction(\n            image_id=None if is_train else row.image_id,\n            dataset=row.dataset,\n            filename=row.image,\n        )\n    )\nsamples = dict(samples)\n\nfor dataset in samples:\n    print(f'Dataset \"{dataset}\" -> num_images={len(samples[dataset])}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-01T00:03:04.929264Z","iopub.execute_input":"2026-07-01T00:03:04.929594Z","iopub.status.idle":"2026-07-01T00:03:05.13512Z","shell.execute_reply.started":"2026-07-01T00:03:04.929565Z","shell.execute_reply":"2026-07-01T00:03:05.134337Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# === Κύριος βρόχος: shortlist -> features -> matching -> COLMAP -> reconstruction ===\ngc.collect()\n\nmax_images = None           # μόνο για debugging: κράτα τις πρώτες N εικόνες (None = όλες)\ndatasets_to_process = None  # None = όλα τα datasets\n\nif is_train:\n    # Στο training μπορεί να φτάσουμε το time limit -> δούλεψε σε λίγα datasets.\n    datasets_to_process = [\n        'amy_gardens',\n        'ETs',\n        'fbk_vineyard',\n        'stairs',\n        # --- Δεδομένα από IMC 2023/2024 (σε σχόλιο = παράλειψη) ---\n        # 'imc2024_dioscuri_baalshamin',\n        # 'imc2023_theather_imc2024_church',\n        # 'imc2023_heritage',\n        # 'imc2023_haiper',\n        # 'imc2024_lizard_pond',\n        # --- Crowdsourced PhotoTourism ---\n        # 'pt_stpeters_stpauls',\n        # 'pt_brandenburg_british_buckingham',\n        # 'pt_piazzasanmarco_grandplace',\n        # 'pt_sacrecoeur_trevi_tajmahal',\n    ]\n\n# Χρόνοι ανά στάδιο (για αναφορά στο τέλος).\ntimings = {k: [] for k in ['shortlisting', 'feature_detection',\n                           'feature_matching', 'RANSAC', 'Reconstruction']}\nresult_log = []\n\nprint(f'Επεξεργασία στο device {device}')\nfor dataset, predictions in samples.items():\n    # Παράλειψη datasets που δεν θέλουμε.\n    if datasets_to_process and dataset not in datasets_to_process:\n        print(f'Παράλειψη \"{dataset}\"')\n        continue\n\n    images_dir = os.path.join(data_dir, 'train' if is_train else 'test', dataset)\n    images = [os.path.join(images_dir, p.filename) for p in predictions]\n    if max_images is not None:\n        images = images[:max_images]\n\n    print(f'\\nΕπεξεργασία dataset \"{dataset}\": {len(images)} εικόνες')\n\n    # Χάρτης: όνομα αρχείου -> θέση στη λίστα predictions (για να γράψουμε τα αποτελέσματα).\n    filename_to_index = {p.filename: idx for idx, p in enumerate(predictions)}\n\n    feature_dir = os.path.join(workdir, 'featureout', dataset)\n    os.makedirs(feature_dir, exist_ok=True)\n\n    # try/except: αν \"σκάσει\" ένα scene, συνεχίζουμε με τα υπόλοιπα.\n    try:\n        # 1) Shortlisting ζευγαριών με DINOv2.\n        t = time()\n        pairs_to_match = build_pair_shortlist(\n            images,\n            sim_th=0.8,             # αυστηρό κατώφλι ομοιότητας (εύρος ~0.2-0.8)\n            min_pairs=20,           # τουλάχιστον τόσα ζευγάρια ανά εικόνα (εύρος ~10-50)\n            exhaustive_if_less=20,  # κάτω από τόσες εικόνες -> εξαντλητικά (εύρος ~10-50)\n            device=device,\n        )\n        timings['shortlisting'].append(time() - t)\n        print(f'Shortlisting: {len(pairs_to_match)} ζευγάρια σε {time() - t:.4f} sec')\n        gc.collect()\n\n        # 2) Ανίχνευση χαρακτηριστικών με ALIKED.\n        t = time()\n        extract_aliked_features(images, feature_dir, num_features=4096, device=device)\n        timings['feature_detection'].append(time() - t)\n        print(f'Features σε {time() - t:.4f} sec')\n        gc.collect()\n\n        # 3) Αντιστοίχιση με LightGlue.\n        t = time()\n        match_pairs_lightglue(images, pairs_to_match, feature_dir=feature_dir,\n                              device=device, verbose=False)\n        timings['feature_matching'].append(time() - t)\n        print(f'Matching σε {time() - t:.4f} sec')\n\n        # 4) Εισαγωγή σε βάση COLMAP.\n        database_path = os.path.join(feature_dir, 'colmap.db')\n        if os.path.isfile(database_path):\n            os.remove(database_path)   # καθάρισμα παλιάς βάσης\n        gc.collect()\n        sleep(1)\n        populate_colmap_database(images_dir, feature_dir=feature_dir, database_path=database_path)\n        output_path = os.path.join(feature_dir, 'colmap_rec_aliked')\n\n        # 5) Geometric verification (RANSAC) πάνω στα matches.\n        t = time()\n        pycolmap.match_exhaustive(database_path)\n        timings['RANSAC'].append(time() - t)\n        print(f'RANSAC σε {time() - t:.4f} sec')\n\n        # 6) Incremental Structure-from-Motion.\n        mapper_options = pycolmap.IncrementalPipelineOptions()\n        mapper_options.min_model_size = 3   # ελάχιστες εικόνες ανά μοντέλο (default 10, εύρος ~3-10)\n        mapper_options.max_num_models = 25  # μέγιστος αριθμός clusters (εύρος ~5-50)\n        os.makedirs(output_path, exist_ok=True)\n\n        t = time()\n        maps = pycolmap.incremental_mapping(\n            database_path=database_path,\n            image_path=images_dir,\n            output_path=output_path,\n            options=mapper_options,\n        )\n        sleep(1)\n        timings['Reconstruction'].append(time() - t)\n        print(f'Reconstruction σε {time() - t:.4f} sec')\n        print(maps)\n\n        clear_output(wait=False)  # καθάρισμα output ώστε να μη γεμίσει η οθόνη\n\n        # 7) Καταγραφή πόζας κάμερας (rotation/translation) για κάθε εγγεγραμμένη εικόνα.\n        registered = 0\n        for map_index, cur_map in maps.items():\n            for _, image in cur_map.images.items():\n                pred_idx = filename_to_index[image.name]\n                predictions[pred_idx].cluster_index = map_index\n                predictions[pred_idx].rotation = deepcopy(image.cam_from_world.rotation.matrix())\n                predictions[pred_idx].translation = deepcopy(image.cam_from_world.translation)\n                registered += 1\n\n        msg = f'Dataset \"{dataset}\" -> Registered {registered}/{len(images)} εικόνες σε {len(maps)} clusters'\n        result_log.append(msg)\n        print(msg)\n        gc.collect()\n\n    except Exception as e:\n        print(e)\n        msg = f'Dataset \"{dataset}\" -> Απέτυχε!'\n        result_log.append(msg)\n        print(msg)\n\n# Σύνοψη αποτελεσμάτων & χρόνων.\nprint('\\nΑποτελέσματα')\nfor s in result_log:\n    print(s)\n\nprint('\\nΧρόνοι')\nfor k, v in timings.items():\n    print(f'{k} -> σύνολο={sum(v):.02f} sec.')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-01T00:03:05.136428Z","iopub.execute_input":"2026-07-01T00:03:05.136701Z","iopub.status.idle":"2026-07-01T00:06:40.201132Z","shell.execute_reply.started":"2026-07-01T00:03:05.136679Z","shell.execute_reply":"2026-07-01T00:06:40.200301Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# === Δημιουργία αρχείου submission ===\n\n# Βοηθητικά: array -> \"a;b;c\" και None -> \"nan;nan;...\"\nto_str = lambda arr: ';'.join(f'{x:.09f}' for x in arr)\nnan_str = lambda n: ';'.join(['nan'] * n)\n\nsubmission_file = '/kaggle/working/submission.csv'\nwith open(submission_file, 'w') as f:\n    # Διαφορετική κεφαλίδα/στήλες για train vs test.\n    if is_train:\n        f.write('dataset,scene,image,rotation_matrix,translation_vector\\n')\n    else:\n        f.write('image_id,dataset,scene,image,rotation_matrix,translation_vector\\n')\n\n    for dataset in samples:\n        for pred in samples[dataset]:\n            # Εικόνες χωρίς cluster -> outliers.\n            scene = 'outliers' if pred.cluster_index is None else f'cluster{pred.cluster_index}'\n            rotation = nan_str(9) if pred.rotation is None else to_str(pred.rotation.flatten())\n            translation = nan_str(3) if pred.translation is None else to_str(pred.translation)\n\n            if is_train:\n                f.write(f'{pred.dataset},{scene},{pred.filename},{rotation},{translation}\\n')\n            else:\n                f.write(f'{pred.image_id},{pred.dataset},{scene},{pred.filename},{rotation},{translation}\\n')\n\n# Γρήγορος έλεγχος των πρώτων γραμμών.\n!head {submission_file}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-01T00:06:40.202064Z","iopub.execute_input":"2026-07-01T00:06:40.202454Z","iopub.status.idle":"2026-07-01T00:06:40.378127Z","shell.execute_reply.started":"2026-07-01T00:06:40.202395Z","shell.execute_reply":"2026-07-01T00:06:40.377182Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# === Υπολογισμός score (μόνο στο training set) ===\n# ΜΗΝ το τρέχεις κατά την υποβολή -- αρκεί να αποθηκευτεί το /kaggle/working/submission.csv.\n\nif is_train:\n    t = time()\n    final_score, dataset_scores = metric.score(\n        gt_csv='/kaggle/input/image-matching-challenge-2025/train_labels.csv',\n        user_csv=submission_file,\n        thresholds_csv='/kaggle/input/image-matching-challenge-2025/train_thresholds.csv',\n        mask_csv=None if is_train else os.path.join(data_dir, 'mask.csv'),\n        inl_cf=0,     # παράμετροι μετρικής (σταθερές από τον διαγωνισμό)\n        strict_cf=-1,\n        verbose=True,\n    )\n    print(f'Score υπολογίστηκε σε {time() - t:.02f} sec.')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-01T00:06:40.379089Z","iopub.execute_input":"2026-07-01T00:06:40.379437Z","iopub.status.idle":"2026-07-01T00:06:40.385342Z","shell.execute_reply.started":"2026-07-01T00:06:40.379401Z","shell.execute_reply":"2026-07-01T00:06:40.384528Z"}},"outputs":[],"execution_count":null}]}