Map query data onto a reference#

Open in Colab

A common situation: you have a paired multimodal reference, and new query data where only one modality was measured (scRNA-seq only, or scATAC-seq only). Because each modality has its own encoder into a shared latent space, UniVI can place query cells next to the reference without retraining. This is the approach used for the bridge analyses in the paper (Figs. 5 and 7).

To be able to check the answers, we build the scenario from the Multiome PBMC data:

  • the reference is 70% of cells, with RNA and ATAC

  • the remaining cells are split into an RNA-only query and an ATAC-only query (we hide the other modality, but keep it for evaluation)

  • one cell type is left out of the reference entirely, to show how to recognize cells the reference has never seen

You will transfer labels to the queries, estimate confidence, and predict the missing modality.

import sys

if "google.colab" in sys.modules:
    %pip install -q "univi[tutorials]>=1.0" "pandas==2.2.3"
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import scanpy as sc
import torch
from sklearn.metrics import confusion_matrix
from sklearn.neighbors import KNeighborsClassifier

import univi.datasets as uds
from univi import ModalityConfig, TrainingConfig, UniVIConfig, UniVIMultiModalVAE, UniVITrainer
from univi.evaluation import cross_modal_predict, encode_adata, encode_fused_adata_pair, pearson_corr_per_feature
from univi.plotting import plot_confusion_matrix
from univi.preprocessing import ATACPreprocessor, RNAPreprocessor
from univi.utils.seed import set_seed
from univi.workflows import make_loader, stack_embeddings

device = "cuda" if torch.cuda.is_available() else ("mps" if torch.backends.mps.is_available() else "cpu")
set_seed(0)
C:\Users\ashfo\micromamba\envs\univi-release-050\Lib\site-packages\tqdm\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
  from .autonotebook import tqdm as notebook_tqdm
N_EPOCHS = 400
BATCH_SIZE = 256
N_HVG = 2000
N_LSI = 101
HOLDOUT_TYPE = "pDC"   # a cell type kept out of the reference

Build reference and query cohorts#

data = uds.pbmc_multiome_10k()
rna, atac = data["rna"], data["atac"]
cell_type = rna.obs["cell_type"].astype(str)

rng = np.random.default_rng(0)
is_query = rng.random(rna.n_obs) < 0.30
is_query |= (cell_type == HOLDOUT_TYPE).to_numpy()          # the held-out type only appears in queries
query_idx = np.flatnonzero(is_query)
rng.shuffle(query_idx)
rna_only_idx, atac_only_idx = np.sort(query_idx[::2]), np.sort(query_idx[1::2])
ref_idx = np.flatnonzero(~is_query)

print(f"reference {len(ref_idx)} cells (RNA + ATAC); RNA-only query {len(rna_only_idx)}; ATAC-only query {len(atac_only_idx)}")
print(f"{HOLDOUT_TYPE} cells in reference: {(cell_type.iloc[ref_idx] == HOLDOUT_TYPE).sum()}")
reference 6668 cells (RNA + ATAC); RNA-only query 1482; ATAC-only query 1481
pDC cells in reference: 0

Train the reference#

Preprocessing is fit on reference training cells. Everything downstream (validation cells and both queries) is transformed with the same fitted objects, exactly as you would do for data collected later.

ref_rna, ref_atac = rna[ref_idx].copy(), atac[ref_idx].copy()
perm = rng.permutation(len(ref_idx))
val_cells, train_cells = np.sort(perm[: len(perm) // 10]), np.sort(perm[len(perm) // 10:])  # 90/10

rna_prep = RNAPreprocessor(n_hvg=N_HVG, scale=True).fit(ref_rna[train_cells])
atac_prep = ATACPreprocessor(n_components=N_LSI, drop_first=True, scale=True).fit(ref_atac[train_cells])

ref = {"rna": rna_prep.transform(ref_rna), "atac": atac_prep.transform(ref_atac)}
train = {m: a[train_cells] for m, a in ref.items()}
val = {m: a[val_cells] for m, a in ref.items()}

cfg = UniVIConfig(
    latent_dim=30, beta=1.25, gamma=4.35, encoder_dropout=0.10, decoder_dropout=0.05,
    kl_anneal_start=50, kl_anneal_end=85, align_anneal_start=75, align_anneal_end=110,
    modalities=[
        ModalityConfig("rna", train["rna"].n_vars, [512, 256, 128], [128, 256, 512]),
        ModalityConfig("atac", train["atac"].n_vars, [128, 64], [64, 128]),
    ],
)
model = UniVIMultiModalVAE(cfg, loss_mode="v1", v1_recon="avg", normalize_v1_terms=True)
UniVITrainer(
    model,
    train_loader=make_loader(train, batch_size=BATCH_SIZE, shuffle=True, drop_last=True),
    val_loader=make_loader(val, batch_size=1024),
    train_cfg=TrainingConfig(n_epochs=N_EPOCHS, batch_size=BATCH_SIZE, lr=1e-3, weight_decay=1e-4, device=device,
                             early_stopping=True, patience=50, best_epoch_warmup=110, log_every=50),
).fit();
[2026-09-20 21:11:22,075] [UniVITrainer] [INFO] TrainingConfig:
[2026-09-20 21:11:22,076] [UniVITrainer] [INFO]   n_epochs: 400
[2026-09-20 21:11:22,076] [UniVITrainer] [INFO]   batch_size: 256
[2026-09-20 21:11:22,076] [UniVITrainer] [INFO]   lr: 0.001
[2026-09-20 21:11:22,077] [UniVITrainer] [INFO]   weight_decay: 0.0001
[2026-09-20 21:11:22,077] [UniVITrainer] [INFO]   device: 'cuda'
[2026-09-20 21:11:22,078] [UniVITrainer] [INFO]   log_every: 50
[2026-09-20 21:11:22,078] [UniVITrainer] [INFO]   grad_clip: None
[2026-09-20 21:11:22,078] [UniVITrainer] [INFO]   num_workers: 0
[2026-09-20 21:11:22,079] [UniVITrainer] [INFO]   seed: 0
[2026-09-20 21:11:22,079] [UniVITrainer] [INFO]   early_stopping: True
[2026-09-20 21:11:22,079] [UniVITrainer] [INFO]   patience: 50
[2026-09-20 21:11:22,079] [UniVITrainer] [INFO]   min_delta: 0.0
[2026-09-20 21:11:22,080] [UniVITrainer] [INFO]   best_epoch_warmup: 110
Training UniVI:   0%|          | 0/400 [00:00<?, ?it/s]
[2026-09-20 21:12:40,491] [UniVITrainer] [INFO] [Epoch 001] Train loss=1040.7768 (beta=0.000, gamma=0.000)
[2026-09-20 21:12:41,723] [UniVITrainer] [INFO] [Epoch 001] Val loss=3808.8118 (beta=1.250, gamma=4.350)
Training UniVI:   0%|          | 0/400 [01:19<?, ?it/s, beta=0.000, gamma=0.000, train_loss=1040.7768, val_loss=3808.8118]
Training UniVI:   0%|          | 1/400 [01:19<8:49:37, 79.64s/it, beta=0.000, gamma=0.000, train_loss=1040.7768, val_loss=3808.8118]
Training UniVI:   0%|          | 1/400 [02:41<8:49:37, 79.64s/it, beta=0.000, gamma=0.000, train_loss=988.6173, val_loss=1756.9209]
Training UniVI:   0%|          | 2/400 [02:41<8:57:54, 81.09s/it, beta=0.000, gamma=0.000, train_loss=988.6173, val_loss=1756.9209]
Training UniVI:   0%|          | 2/400 [04:05<8:57:54, 81.09s/it, beta=0.000, gamma=0.000, train_loss=971.3029, val_loss=1443.8705]
Training UniVI:   1%|          | 3/400 [04:05<9:03:10, 82.09s/it, beta=0.000, gamma=0.000, train_loss=971.3029, val_loss=1443.8705]
Training UniVI:   1%|          | 3/400 [05:26<9:03:10, 82.09s/it, beta=0.000, gamma=0.000, train_loss=958.3296, val_loss=1436.9366]
Training UniVI:   1%|          | 4/400 [05:26<9:00:15, 81.86s/it, beta=0.000, gamma=0.000, train_loss=958.3296, val_loss=1436.9366]
Training UniVI:   1%|          | 4/400 [06:46<9:00:15, 81.86s/it, beta=0.000, gamma=0.000, train_loss=952.7201, val_loss=1435.5139]
Training UniVI:   1%|▏         | 5/400 [06:46<8:53:14, 81.00s/it, beta=0.000, gamma=0.000, train_loss=952.7201, val_loss=1435.5139]
Training UniVI:   1%|▏         | 5/400 [08:10<8:53:14, 81.00s/it, beta=0.000, gamma=0.000, train_loss=948.1868, val_loss=1870.4937]
Training UniVI:   2%|▏         | 6/400 [08:10<8:59:32, 82.16s/it, beta=0.000, gamma=0.000, train_loss=948.1868, val_loss=1870.4937]
Training UniVI:   2%|▏         | 6/400 [09:33<8:59:32, 82.16s/it, beta=0.000, gamma=0.000, train_loss=939.1067, val_loss=1895.1725]
Training UniVI:   2%|▏         | 7/400 [09:33<8:59:27, 82.36s/it, beta=0.000, gamma=0.000, train_loss=939.1067, val_loss=1895.1725]
Training UniVI:   2%|▏         | 7/400 [10:51<8:59:27, 82.36s/it, beta=0.000, gamma=0.000, train_loss=939.4732, val_loss=2069.8594]
Training UniVI:   2%|▏         | 8/400 [10:51<8:49:41, 81.08s/it, beta=0.000, gamma=0.000, train_loss=939.4732, val_loss=2069.8594]
Training UniVI:   2%|▏         | 8/400 [12:16<8:49:41, 81.08s/it, beta=0.000, gamma=0.000, train_loss=935.5894, val_loss=2417.6377]
Training UniVI:   2%|▏         | 9/400 [12:16<8:55:43, 82.21s/it, beta=0.000, gamma=0.000, train_loss=935.5894, val_loss=2417.6377]
Training UniVI:   2%|▏         | 9/400 [13:36<8:55:43, 82.21s/it, beta=0.000, gamma=0.000, train_loss=927.7715, val_loss=16851.0039]
Training UniVI:   2%|▎         | 10/400 [13:36<8:50:10, 81.56s/it, beta=0.000, gamma=0.000, train_loss=927.7715, val_loss=16851.0039]
Training UniVI:   2%|▎         | 10/400 [14:56<8:50:10, 81.56s/it, beta=0.000, gamma=0.000, train_loss=924.7392, val_loss=88931.2188]
Training UniVI:   3%|▎         | 11/400 [14:56<8:46:00, 81.13s/it, beta=0.000, gamma=0.000, train_loss=924.7392, val_loss=88931.2188]
Training UniVI:   3%|▎         | 11/400 [16:16<8:46:00, 81.13s/it, beta=0.000, gamma=0.000, train_loss=920.4775, val_loss=52211.4023]
Training UniVI:   3%|▎         | 12/400 [16:16<8:41:52, 80.70s/it, beta=0.000, gamma=0.000, train_loss=920.4775, val_loss=52211.4023]
Training UniVI:   3%|▎         | 12/400 [17:36<8:41:52, 80.70s/it, beta=0.000, gamma=0.000, train_loss=918.5225, val_loss=23372.2910]
Training UniVI:   3%|▎         | 13/400 [17:36<8:39:19, 80.51s/it, beta=0.000, gamma=0.000, train_loss=918.5225, val_loss=23372.2910]
Training UniVI:   3%|▎         | 13/400 [18:57<8:39:19, 80.51s/it, beta=0.000, gamma=0.000, train_loss=917.0590, val_loss=10203.7568]
Training UniVI:   4%|▎         | 14/400 [18:57<8:40:05, 80.84s/it, beta=0.000, gamma=0.000, train_loss=917.0590, val_loss=10203.7568]
Training UniVI:   4%|▎         | 14/400 [20:20<8:40:05, 80.84s/it, beta=0.000, gamma=0.000, train_loss=916.8823, val_loss=22194.3164]
Training UniVI:   4%|▍         | 15/400 [20:20<8:41:49, 81.32s/it, beta=0.000, gamma=0.000, train_loss=916.8823, val_loss=22194.3164]
Training UniVI:   4%|▍         | 15/400 [21:43<8:41:49, 81.32s/it, beta=0.000, gamma=0.000, train_loss=912.5656, val_loss=16930.9414]
Training UniVI:   4%|▍         | 16/400 [21:43<8:44:24, 81.94s/it, beta=0.000, gamma=0.000, train_loss=912.5656, val_loss=16930.9414]
Training UniVI:   4%|▍         | 16/400 [23:04<8:44:24, 81.94s/it, beta=0.000, gamma=0.000, train_loss=913.1345, val_loss=23625.1094]
Training UniVI:   4%|▍         | 17/400 [23:04<8:41:12, 81.65s/it, beta=0.000, gamma=0.000, train_loss=913.1345, val_loss=23625.1094]
Training UniVI:   4%|▍         | 17/400 [24:21<8:41:12, 81.65s/it, beta=0.000, gamma=0.000, train_loss=917.3358, val_loss=17593.3535]
Training UniVI:   4%|▍         | 18/400 [24:21<8:31:32, 80.35s/it, beta=0.000, gamma=0.000, train_loss=917.3358, val_loss=17593.3535]
Training UniVI:   4%|▍         | 18/400 [25:38<8:31:32, 80.35s/it, beta=0.000, gamma=0.000, train_loss=912.4139, val_loss=24268.8535]
Training UniVI:   5%|▍         | 19/400 [25:38<8:22:51, 79.19s/it, beta=0.000, gamma=0.000, train_loss=912.4139, val_loss=24268.8535]
Training UniVI:   5%|▍         | 19/400 [26:57<8:22:51, 79.19s/it, beta=0.000, gamma=0.000, train_loss=909.5987, val_loss=34190.4805]
Training UniVI:   5%|▌         | 20/400 [26:57<8:20:42, 79.06s/it, beta=0.000, gamma=0.000, train_loss=909.5987, val_loss=34190.4805]
Training UniVI:   5%|▌         | 20/400 [28:19<8:20:42, 79.06s/it, beta=0.000, gamma=0.000, train_loss=906.1858, val_loss=23320.0156]
Training UniVI:   5%|▌         | 21/400 [28:19<8:25:46, 80.07s/it, beta=0.000, gamma=0.000, train_loss=906.1858, val_loss=23320.0156]
Training UniVI:   5%|▌         | 21/400 [29:41<8:25:46, 80.07s/it, beta=0.000, gamma=0.000, train_loss=908.4308, val_loss=27340.9805]
Training UniVI:   6%|▌         | 22/400 [29:41<8:28:09, 80.66s/it, beta=0.000, gamma=0.000, train_loss=908.4308, val_loss=27340.9805]
Training UniVI:   6%|▌         | 22/400 [31:03<8:28:09, 80.66s/it, beta=0.000, gamma=0.000, train_loss=906.6592, val_loss=25585.2832]
Training UniVI:   6%|▌         | 23/400 [31:03<8:29:22, 81.07s/it, beta=0.000, gamma=0.000, train_loss=906.6592, val_loss=25585.2832]
Training UniVI:   6%|▌         | 23/400 [32:26<8:29:22, 81.07s/it, beta=0.000, gamma=0.000, train_loss=905.6733, val_loss=28755.2383]
Training UniVI:   6%|▌         | 24/400 [32:26<8:31:51, 81.68s/it, beta=0.000, gamma=0.000, train_loss=905.6733, val_loss=28755.2383]
Training UniVI:   6%|▌         | 24/400 [33:48<8:31:51, 81.68s/it, beta=0.000, gamma=0.000, train_loss=903.7709, val_loss=29737.0879]
Training UniVI:   6%|▋         | 25/400 [33:48<8:30:00, 81.60s/it, beta=0.000, gamma=0.000, train_loss=903.7709, val_loss=29737.0879]
Training UniVI:   6%|▋         | 25/400 [35:08<8:30:00, 81.60s/it, beta=0.000, gamma=0.000, train_loss=902.2310, val_loss=34254.7227]
Training UniVI:   6%|▋         | 26/400 [35:08<8:25:21, 81.07s/it, beta=0.000, gamma=0.000, train_loss=902.2310, val_loss=34254.7227]
Training UniVI:   6%|▋         | 26/400 [36:31<8:25:21, 81.07s/it, beta=0.000, gamma=0.000, train_loss=902.2070, val_loss=28148.8809]
Training UniVI:   7%|▋         | 27/400 [36:31<8:28:20, 81.77s/it, beta=0.000, gamma=0.000, train_loss=902.2070, val_loss=28148.8809]
Training UniVI:   7%|▋         | 27/400 [37:55<8:28:20, 81.77s/it, beta=0.000, gamma=0.000, train_loss=902.6281, val_loss=28064.0840]
Training UniVI:   7%|▋         | 28/400 [37:55<8:31:15, 82.46s/it, beta=0.000, gamma=0.000, train_loss=902.6281, val_loss=28064.0840]
Training UniVI:   7%|▋         | 28/400 [39:19<8:31:15, 82.46s/it, beta=0.000, gamma=0.000, train_loss=901.1537, val_loss=38076.1094]
Training UniVI:   7%|▋         | 29/400 [39:19<8:32:55, 82.95s/it, beta=0.000, gamma=0.000, train_loss=901.1537, val_loss=38076.1094]
Training UniVI:   7%|▋         | 29/400 [40:42<8:32:55, 82.95s/it, beta=0.000, gamma=0.000, train_loss=901.1755, val_loss=43714.9727]
Training UniVI:   8%|▊         | 30/400 [40:42<8:31:09, 82.89s/it, beta=0.000, gamma=0.000, train_loss=901.1755, val_loss=43714.9727]
Training UniVI:   8%|▊         | 30/400 [42:05<8:31:09, 82.89s/it, beta=0.000, gamma=0.000, train_loss=899.3554, val_loss=32548.3496]
Training UniVI:   8%|▊         | 31/400 [42:05<8:29:44, 82.89s/it, beta=0.000, gamma=0.000, train_loss=899.3554, val_loss=32548.3496]
Training UniVI:   8%|▊         | 31/400 [43:28<8:29:44, 82.89s/it, beta=0.000, gamma=0.000, train_loss=898.3478, val_loss=39519.0625]
Training UniVI:   8%|▊         | 32/400 [43:28<8:29:53, 83.14s/it, beta=0.000, gamma=0.000, train_loss=898.3478, val_loss=39519.0625]
Training UniVI:   8%|▊         | 32/400 [44:50<8:29:53, 83.14s/it, beta=0.000, gamma=0.000, train_loss=897.4538, val_loss=41219.5312]
Training UniVI:   8%|▊         | 33/400 [44:50<8:24:35, 82.50s/it, beta=0.000, gamma=0.000, train_loss=897.4538, val_loss=41219.5312]
Training UniVI:   8%|▊         | 33/400 [46:09<8:24:35, 82.50s/it, beta=0.000, gamma=0.000, train_loss=897.1977, val_loss=45491.6758]
Training UniVI:   8%|▊         | 34/400 [46:09<8:17:19, 81.53s/it, beta=0.000, gamma=0.000, train_loss=897.1977, val_loss=45491.6758]
Training UniVI:   8%|▊         | 34/400 [47:32<8:17:19, 81.53s/it, beta=0.000, gamma=0.000, train_loss=896.7340, val_loss=43548.0625]
Training UniVI:   9%|▉         | 35/400 [47:32<8:18:36, 81.96s/it, beta=0.000, gamma=0.000, train_loss=896.7340, val_loss=43548.0625]
Training UniVI:   9%|▉         | 35/400 [48:51<8:18:36, 81.96s/it, beta=0.000, gamma=0.000, train_loss=895.1888, val_loss=41813.8047]
Training UniVI:   9%|▉         | 36/400 [48:51<8:12:36, 81.20s/it, beta=0.000, gamma=0.000, train_loss=895.1888, val_loss=41813.8047]
Training UniVI:   9%|▉         | 36/400 [50:12<8:12:36, 81.20s/it, beta=0.000, gamma=0.000, train_loss=893.2009, val_loss=46063.3477]
Training UniVI:   9%|▉         | 37/400 [50:12<8:10:11, 81.02s/it, beta=0.000, gamma=0.000, train_loss=893.2009, val_loss=46063.3477]
Training UniVI:   9%|▉         | 37/400 [51:33<8:10:11, 81.02s/it, beta=0.000, gamma=0.000, train_loss=894.7546, val_loss=51936.2422]
Training UniVI:  10%|▉         | 38/400 [51:33<8:09:17, 81.10s/it, beta=0.000, gamma=0.000, train_loss=894.7546, val_loss=51936.2422]
Training UniVI:  10%|▉         | 38/400 [52:54<8:09:17, 81.10s/it, beta=0.000, gamma=0.000, train_loss=896.5409, val_loss=49196.1953]
Training UniVI:  10%|▉         | 39/400 [52:54<8:07:31, 81.03s/it, beta=0.000, gamma=0.000, train_loss=896.5409, val_loss=49196.1953]
Training UniVI:  10%|▉         | 39/400 [54:18<8:07:31, 81.03s/it, beta=0.000, gamma=0.000, train_loss=893.5614, val_loss=55596.6797]
Training UniVI:  10%|█         | 40/400 [54:18<8:11:06, 81.85s/it, beta=0.000, gamma=0.000, train_loss=893.5614, val_loss=55596.6797]
Training UniVI:  10%|█         | 40/400 [55:36<8:11:06, 81.85s/it, beta=0.000, gamma=0.000, train_loss=891.8223, val_loss=55757.5586]
Training UniVI:  10%|█         | 41/400 [55:36<8:03:04, 80.74s/it, beta=0.000, gamma=0.000, train_loss=891.8223, val_loss=55757.5586]
Training UniVI:  10%|█         | 41/400 [56:53<8:03:04, 80.74s/it, beta=0.000, gamma=0.000, train_loss=893.5015, val_loss=63437.2617]
Training UniVI:  10%|█         | 42/400 [56:53<7:54:30, 79.53s/it, beta=0.000, gamma=0.000, train_loss=893.5015, val_loss=63437.2617]
Training UniVI:  10%|█         | 42/400 [58:09<7:54:30, 79.53s/it, beta=0.000, gamma=0.000, train_loss=892.3999, val_loss=69834.7500]
Training UniVI:  11%|█         | 43/400 [58:09<7:48:30, 78.74s/it, beta=0.000, gamma=0.000, train_loss=892.3999, val_loss=69834.7500]
Training UniVI:  11%|█         | 43/400 [59:30<7:48:30, 78.74s/it, beta=0.000, gamma=0.000, train_loss=888.8244, val_loss=70493.4219]
Training UniVI:  11%|█         | 44/400 [59:30<7:50:10, 79.24s/it, beta=0.000, gamma=0.000, train_loss=888.8244, val_loss=70493.4219]
Training UniVI:  11%|█         | 44/400 [1:00:51<7:50:10, 79.24s/it, beta=0.000, gamma=0.000, train_loss=892.4919, val_loss=73207.2188]
Training UniVI:  11%|█▏        | 45/400 [1:00:51<7:52:11, 79.81s/it, beta=0.000, gamma=0.000, train_loss=892.4919, val_loss=73207.2188]
Training UniVI:  11%|█▏        | 45/400 [1:02:13<7:52:11, 79.81s/it, beta=0.000, gamma=0.000, train_loss=891.0387, val_loss=94155.5000]
Training UniVI:  12%|█▏        | 46/400 [1:02:13<7:55:19, 80.56s/it, beta=0.000, gamma=0.000, train_loss=891.0387, val_loss=94155.5000]
Training UniVI:  12%|█▏        | 46/400 [1:03:34<7:55:19, 80.56s/it, beta=0.000, gamma=0.000, train_loss=889.4673, val_loss=85486.1250]
Training UniVI:  12%|█▏        | 47/400 [1:03:34<7:54:42, 80.69s/it, beta=0.000, gamma=0.000, train_loss=889.4673, val_loss=85486.1250]
Training UniVI:  12%|█▏        | 47/400 [1:04:57<7:54:42, 80.69s/it, beta=0.000, gamma=0.000, train_loss=888.1356, val_loss=90367.0234]
Training UniVI:  12%|█▏        | 48/400 [1:04:57<7:57:29, 81.39s/it, beta=0.000, gamma=0.000, train_loss=888.1356, val_loss=90367.0234]
Training UniVI:  12%|█▏        | 48/400 [1:06:20<7:57:29, 81.39s/it, beta=0.000, gamma=0.000, train_loss=887.9985, val_loss=90416.6875]
Training UniVI:  12%|█▏        | 49/400 [1:06:20<7:57:50, 81.68s/it, beta=0.000, gamma=0.000, train_loss=887.9985, val_loss=90416.6875]
[2026-09-20 22:19:03,867] [UniVITrainer] [INFO] [Epoch 050] Train loss=889.1440 (beta=0.000, gamma=0.000)
[2026-09-20 22:19:05,140] [UniVITrainer] [INFO] [Epoch 050] Val loss=106877.4062 (beta=1.250, gamma=4.350)
Training UniVI:  12%|█▏        | 49/400 [1:07:43<7:57:50, 81.68s/it, beta=0.000, gamma=0.000, train_loss=889.1440, val_loss=106877.4062]
Training UniVI:  12%|█▎        | 50/400 [1:07:43<7:58:35, 82.04s/it, beta=0.000, gamma=0.000, train_loss=889.1440, val_loss=106877.4062]
Training UniVI:  12%|█▎        | 50/400 [1:09:05<7:58:35, 82.04s/it, beta=0.036, gamma=0.000, train_loss=889.7471, val_loss=47747.7969]
Training UniVI:  13%|█▎        | 51/400 [1:09:05<7:57:36, 82.11s/it, beta=0.036, gamma=0.000, train_loss=889.7471, val_loss=47747.7969]
Training UniVI:  13%|█▎        | 51/400 [1:10:25<7:57:36, 82.11s/it, beta=0.071, gamma=0.000, train_loss=894.3282, val_loss=8193.1357]
Training UniVI:  13%|█▎        | 52/400 [1:10:25<7:52:04, 81.39s/it, beta=0.071, gamma=0.000, train_loss=894.3282, val_loss=8193.1357]
Training UniVI:  13%|█▎        | 52/400 [1:11:45<7:52:04, 81.39s/it, beta=0.107, gamma=0.000, train_loss=894.7913, val_loss=2729.8037]
Training UniVI:  13%|█▎        | 53/400 [1:11:45<7:48:58, 81.09s/it, beta=0.107, gamma=0.000, train_loss=894.7913, val_loss=2729.8037]
Training UniVI:  13%|█▎        | 53/400 [1:13:06<7:48:58, 81.09s/it, beta=0.143, gamma=0.000, train_loss=894.6963, val_loss=1517.1908]
Training UniVI:  14%|█▎        | 54/400 [1:13:06<7:48:08, 81.18s/it, beta=0.143, gamma=0.000, train_loss=894.6963, val_loss=1517.1908]
Training UniVI:  14%|█▎        | 54/400 [1:14:25<7:48:08, 81.18s/it, beta=0.179, gamma=0.000, train_loss=892.0147, val_loss=1742.0101]
Training UniVI:  14%|█▍        | 55/400 [1:14:25<7:42:05, 80.36s/it, beta=0.179, gamma=0.000, train_loss=892.0147, val_loss=1742.0101]
Training UniVI:  14%|█▍        | 55/400 [1:15:45<7:42:05, 80.36s/it, beta=0.214, gamma=0.000, train_loss=896.3374, val_loss=1104.7943]
Training UniVI:  14%|█▍        | 56/400 [1:15:45<7:40:16, 80.28s/it, beta=0.214, gamma=0.000, train_loss=896.3374, val_loss=1104.7943]
Training UniVI:  14%|█▍        | 56/400 [1:17:04<7:40:16, 80.28s/it, beta=0.250, gamma=0.000, train_loss=898.0994, val_loss=1883.9775]
Training UniVI:  14%|█▍        | 57/400 [1:17:04<7:36:33, 79.86s/it, beta=0.250, gamma=0.000, train_loss=898.0994, val_loss=1883.9775]
Training UniVI:  14%|█▍        | 57/400 [1:18:23<7:36:33, 79.86s/it, beta=0.286, gamma=0.000, train_loss=897.0481, val_loss=1071.9700]
Training UniVI:  14%|█▍        | 58/400 [1:18:23<7:33:22, 79.54s/it, beta=0.286, gamma=0.000, train_loss=897.0481, val_loss=1071.9700]
Training UniVI:  14%|█▍        | 58/400 [1:19:44<7:33:22, 79.54s/it, beta=0.321, gamma=0.000, train_loss=895.1881, val_loss=1276.6823]
Training UniVI:  15%|█▍        | 59/400 [1:19:44<7:36:09, 80.26s/it, beta=0.321, gamma=0.000, train_loss=895.1881, val_loss=1276.6823]
Training UniVI:  15%|█▍        | 59/400 [1:21:04<7:36:09, 80.26s/it, beta=0.357, gamma=0.000, train_loss=898.1480, val_loss=1281.5372]
Training UniVI:  15%|█▌        | 60/400 [1:21:04<7:33:46, 80.08s/it, beta=0.357, gamma=0.000, train_loss=898.1480, val_loss=1281.5372]
Training UniVI:  15%|█▌        | 60/400 [1:22:23<7:33:46, 80.08s/it, beta=0.393, gamma=0.000, train_loss=896.9949, val_loss=1094.2070]
Training UniVI:  15%|█▌        | 61/400 [1:22:23<7:29:44, 79.60s/it, beta=0.393, gamma=0.000, train_loss=896.9949, val_loss=1094.2070]
Training UniVI:  15%|█▌        | 61/400 [1:23:42<7:29:44, 79.60s/it, beta=0.429, gamma=0.000, train_loss=896.9218, val_loss=1074.0802]
Training UniVI:  16%|█▌        | 62/400 [1:23:42<7:27:19, 79.41s/it, beta=0.429, gamma=0.000, train_loss=896.9218, val_loss=1074.0802]
Training UniVI:  16%|█▌        | 62/400 [1:25:01<7:27:19, 79.41s/it, beta=0.464, gamma=0.000, train_loss=898.8290, val_loss=1021.1887]
Training UniVI:  16%|█▌        | 63/400 [1:25:01<7:25:28, 79.31s/it, beta=0.464, gamma=0.000, train_loss=898.8290, val_loss=1021.1887]
Training UniVI:  16%|█▌        | 63/400 [1:26:18<7:25:28, 79.31s/it, beta=0.500, gamma=0.000, train_loss=896.6207, val_loss=987.1913]
Training UniVI:  16%|█▌        | 64/400 [1:26:18<7:20:34, 78.67s/it, beta=0.500, gamma=0.000, train_loss=896.6207, val_loss=987.1913]
Training UniVI:  16%|█▌        | 64/400 [1:27:34<7:20:34, 78.67s/it, beta=0.536, gamma=0.000, train_loss=897.3770, val_loss=984.1365]
Training UniVI:  16%|█▋        | 65/400 [1:27:34<7:14:29, 77.82s/it, beta=0.536, gamma=0.000, train_loss=897.3770, val_loss=984.1365]
Training UniVI:  16%|█▋        | 65/400 [1:28:49<7:14:29, 77.82s/it, beta=0.571, gamma=0.000, train_loss=896.6130, val_loss=979.4181]
Training UniVI:  16%|█▋        | 66/400 [1:28:49<7:09:43, 77.20s/it, beta=0.571, gamma=0.000, train_loss=896.6130, val_loss=979.4181]
Training UniVI:  16%|█▋        | 66/400 [1:30:05<7:09:43, 77.20s/it, beta=0.607, gamma=0.000, train_loss=898.5942, val_loss=968.1669]
Training UniVI:  17%|█▋        | 67/400 [1:30:05<7:06:05, 76.77s/it, beta=0.607, gamma=0.000, train_loss=898.5942, val_loss=968.1669]
Training UniVI:  17%|█▋        | 67/400 [1:31:21<7:06:05, 76.77s/it, beta=0.643, gamma=0.000, train_loss=898.8132, val_loss=977.5410]
Training UniVI:  17%|█▋        | 68/400 [1:31:21<7:02:39, 76.39s/it, beta=0.643, gamma=0.000, train_loss=898.8132, val_loss=977.5410]
Training UniVI:  17%|█▋        | 68/400 [1:32:36<7:02:39, 76.39s/it, beta=0.679, gamma=0.000, train_loss=898.0077, val_loss=968.6613]
Training UniVI:  17%|█▋        | 69/400 [1:32:36<7:00:23, 76.20s/it, beta=0.679, gamma=0.000, train_loss=898.0077, val_loss=968.6613]
Training UniVI:  17%|█▋        | 69/400 [1:33:52<7:00:23, 76.20s/it, beta=0.714, gamma=0.000, train_loss=900.7312, val_loss=961.4399]
Training UniVI:  18%|█▊        | 70/400 [1:33:52<6:58:30, 76.09s/it, beta=0.714, gamma=0.000, train_loss=900.7312, val_loss=961.4399]
Training UniVI:  18%|█▊        | 70/400 [1:35:08<6:58:30, 76.09s/it, beta=0.750, gamma=0.000, train_loss=902.2561, val_loss=953.0550]
Training UniVI:  18%|█▊        | 71/400 [1:35:08<6:56:38, 75.98s/it, beta=0.750, gamma=0.000, train_loss=902.2561, val_loss=953.0550]
Training UniVI:  18%|█▊        | 71/400 [1:36:23<6:56:38, 75.98s/it, beta=0.786, gamma=0.000, train_loss=898.4115, val_loss=950.8370]
Training UniVI:  18%|█▊        | 72/400 [1:36:23<6:54:09, 75.76s/it, beta=0.786, gamma=0.000, train_loss=898.4115, val_loss=950.8370]
Training UniVI:  18%|█▊        | 72/400 [1:37:39<6:54:09, 75.76s/it, beta=0.821, gamma=0.000, train_loss=899.3332, val_loss=946.1103]
Training UniVI:  18%|█▊        | 73/400 [1:37:39<6:52:42, 75.73s/it, beta=0.821, gamma=0.000, train_loss=899.3332, val_loss=946.1103]
Training UniVI:  18%|█▊        | 73/400 [1:38:54<6:52:42, 75.73s/it, beta=0.857, gamma=0.000, train_loss=899.1671, val_loss=940.1567]
Training UniVI:  18%|█▊        | 74/400 [1:38:54<6:50:55, 75.63s/it, beta=0.857, gamma=0.000, train_loss=899.1671, val_loss=940.1567]
Training UniVI:  18%|█▊        | 74/400 [1:40:10<6:50:55, 75.63s/it, beta=0.893, gamma=0.000, train_loss=899.2211, val_loss=940.9665]
Training UniVI:  19%|█▉        | 75/400 [1:40:10<6:49:47, 75.65s/it, beta=0.893, gamma=0.000, train_loss=899.2211, val_loss=940.9665]
Training UniVI:  19%|█▉        | 75/400 [1:41:26<6:49:47, 75.65s/it, beta=0.929, gamma=0.124, train_loss=900.9792, val_loss=925.0312]
Training UniVI:  19%|█▉        | 76/400 [1:41:26<6:48:24, 75.63s/it, beta=0.929, gamma=0.124, train_loss=900.9792, val_loss=925.0312]
Training UniVI:  19%|█▉        | 76/400 [1:42:41<6:48:24, 75.63s/it, beta=0.964, gamma=0.249, train_loss=903.7119, val_loss=920.6160]
Training UniVI:  19%|█▉        | 77/400 [1:42:41<6:47:00, 75.61s/it, beta=0.964, gamma=0.249, train_loss=903.7119, val_loss=920.6160]
Training UniVI:  19%|█▉        | 77/400 [1:43:57<6:47:00, 75.61s/it, beta=1.000, gamma=0.373, train_loss=904.6667, val_loss=914.9917]
Training UniVI:  20%|█▉        | 78/400 [1:43:57<6:45:47, 75.61s/it, beta=1.000, gamma=0.373, train_loss=904.6667, val_loss=914.9917]
Training UniVI:  20%|█▉        | 78/400 [1:45:12<6:45:47, 75.61s/it, beta=1.036, gamma=0.497, train_loss=906.0266, val_loss=914.2607]
Training UniVI:  20%|█▉        | 79/400 [1:45:12<6:44:28, 75.60s/it, beta=1.036, gamma=0.497, train_loss=906.0266, val_loss=914.2607]
Training UniVI:  20%|█▉        | 79/400 [1:46:28<6:44:28, 75.60s/it, beta=1.071, gamma=0.621, train_loss=907.3726, val_loss=913.7003]
Training UniVI:  20%|██        | 80/400 [1:46:28<6:42:55, 75.55s/it, beta=1.071, gamma=0.621, train_loss=907.3726, val_loss=913.7003]
Training UniVI:  20%|██        | 80/400 [1:47:43<6:42:55, 75.55s/it, beta=1.107, gamma=0.746, train_loss=907.2755, val_loss=909.3932]
Training UniVI:  20%|██        | 81/400 [1:47:43<6:41:24, 75.50s/it, beta=1.107, gamma=0.746, train_loss=907.2755, val_loss=909.3932]
Training UniVI:  20%|██        | 81/400 [1:48:59<6:41:24, 75.50s/it, beta=1.143, gamma=0.870, train_loss=906.7248, val_loss=907.6089]
Training UniVI:  20%|██        | 82/400 [1:48:59<6:40:41, 75.60s/it, beta=1.143, gamma=0.870, train_loss=906.7248, val_loss=907.6089]
Training UniVI:  20%|██        | 82/400 [1:50:14<6:40:41, 75.60s/it, beta=1.179, gamma=0.994, train_loss=906.8403, val_loss=906.6877]
Training UniVI:  21%|██        | 83/400 [1:50:14<6:39:00, 75.52s/it, beta=1.179, gamma=0.994, train_loss=906.8403, val_loss=906.6877]
Training UniVI:  21%|██        | 83/400 [1:51:30<6:39:00, 75.52s/it, beta=1.214, gamma=1.119, train_loss=908.1735, val_loss=906.2679]
Training UniVI:  21%|██        | 84/400 [1:51:30<6:37:43, 75.52s/it, beta=1.214, gamma=1.119, train_loss=908.1735, val_loss=906.2679]
Training UniVI:  21%|██        | 84/400 [1:52:46<6:37:43, 75.52s/it, beta=1.250, gamma=1.243, train_loss=910.4707, val_loss=905.4255]
Training UniVI:  21%|██▏       | 85/400 [1:52:46<6:36:45, 75.57s/it, beta=1.250, gamma=1.243, train_loss=910.4707, val_loss=905.4255]
Training UniVI:  21%|██▏       | 85/400 [1:54:01<6:36:45, 75.57s/it, beta=1.250, gamma=1.367, train_loss=910.4406, val_loss=904.3364]
Training UniVI:  22%|██▏       | 86/400 [1:54:01<6:35:37, 75.60s/it, beta=1.250, gamma=1.367, train_loss=910.4406, val_loss=904.3364]
Training UniVI:  22%|██▏       | 86/400 [1:55:17<6:35:37, 75.60s/it, beta=1.250, gamma=1.491, train_loss=909.9715, val_loss=903.5826]
Training UniVI:  22%|██▏       | 87/400 [1:55:17<6:34:11, 75.56s/it, beta=1.250, gamma=1.491, train_loss=909.9715, val_loss=903.5826]
Training UniVI:  22%|██▏       | 87/400 [1:56:33<6:34:11, 75.56s/it, beta=1.250, gamma=1.616, train_loss=909.0621, val_loss=902.4775]
Training UniVI:  22%|██▏       | 88/400 [1:56:33<6:33:24, 75.65s/it, beta=1.250, gamma=1.616, train_loss=909.0621, val_loss=902.4775]
Training UniVI:  22%|██▏       | 88/400 [1:57:48<6:33:24, 75.65s/it, beta=1.250, gamma=1.740, train_loss=907.5612, val_loss=902.3177]
Training UniVI:  22%|██▏       | 89/400 [1:57:48<6:31:58, 75.62s/it, beta=1.250, gamma=1.740, train_loss=907.5612, val_loss=902.3177]
Training UniVI:  22%|██▏       | 89/400 [1:59:04<6:31:58, 75.62s/it, beta=1.250, gamma=1.864, train_loss=907.8579, val_loss=901.9595]
Training UniVI:  22%|██▎       | 90/400 [1:59:04<6:31:02, 75.68s/it, beta=1.250, gamma=1.864, train_loss=907.8579, val_loss=901.9595]
Training UniVI:  22%|██▎       | 90/400 [2:00:19<6:31:02, 75.68s/it, beta=1.250, gamma=1.989, train_loss=909.4022, val_loss=902.3223]
Training UniVI:  23%|██▎       | 91/400 [2:00:19<6:28:53, 75.51s/it, beta=1.250, gamma=1.989, train_loss=909.4022, val_loss=902.3223]
Training UniVI:  23%|██▎       | 91/400 [2:01:34<6:28:53, 75.51s/it, beta=1.250, gamma=2.113, train_loss=908.5205, val_loss=902.0361]
Training UniVI:  23%|██▎       | 92/400 [2:01:34<6:27:27, 75.48s/it, beta=1.250, gamma=2.113, train_loss=908.5205, val_loss=902.0361]
Training UniVI:  23%|██▎       | 92/400 [2:02:50<6:27:27, 75.48s/it, beta=1.250, gamma=2.237, train_loss=908.3062, val_loss=901.0219]
Training UniVI:  23%|██▎       | 93/400 [2:02:50<6:26:26, 75.53s/it, beta=1.250, gamma=2.237, train_loss=908.3062, val_loss=901.0219]
Training UniVI:  23%|██▎       | 93/400 [2:04:06<6:26:26, 75.53s/it, beta=1.250, gamma=2.361, train_loss=908.3920, val_loss=903.1939]
Training UniVI:  24%|██▎       | 94/400 [2:04:06<6:25:10, 75.53s/it, beta=1.250, gamma=2.361, train_loss=908.3920, val_loss=903.1939]
Training UniVI:  24%|██▎       | 94/400 [2:05:21<6:25:10, 75.53s/it, beta=1.250, gamma=2.486, train_loss=906.4568, val_loss=901.8909]
Training UniVI:  24%|██▍       | 95/400 [2:05:21<6:23:35, 75.46s/it, beta=1.250, gamma=2.486, train_loss=906.4568, val_loss=901.8909]
Training UniVI:  24%|██▍       | 95/400 [2:06:36<6:23:35, 75.46s/it, beta=1.250, gamma=2.610, train_loss=908.6862, val_loss=901.8009]
Training UniVI:  24%|██▍       | 96/400 [2:06:36<6:22:22, 75.47s/it, beta=1.250, gamma=2.610, train_loss=908.6862, val_loss=901.8009]
Training UniVI:  24%|██▍       | 96/400 [2:07:52<6:22:22, 75.47s/it, beta=1.250, gamma=2.734, train_loss=910.4001, val_loss=901.4913]
Training UniVI:  24%|██▍       | 97/400 [2:07:52<6:21:09, 75.48s/it, beta=1.250, gamma=2.734, train_loss=910.4001, val_loss=901.4913]
Training UniVI:  24%|██▍       | 97/400 [2:09:08<6:21:09, 75.48s/it, beta=1.250, gamma=2.859, train_loss=907.3135, val_loss=900.8060]
Training UniVI:  24%|██▍       | 98/400 [2:09:08<6:20:26, 75.58s/it, beta=1.250, gamma=2.859, train_loss=907.3135, val_loss=900.8060]
Training UniVI:  24%|██▍       | 98/400 [2:10:23<6:20:26, 75.58s/it, beta=1.250, gamma=2.983, train_loss=907.8843, val_loss=900.4897]
Training UniVI:  25%|██▍       | 99/400 [2:10:23<6:19:19, 75.61s/it, beta=1.250, gamma=2.983, train_loss=907.8843, val_loss=900.4897]
[2026-09-20 23:23:00,497] [UniVITrainer] [INFO] [Epoch 100] Train loss=906.9410 (beta=1.250, gamma=3.107)
[2026-09-20 23:23:01,688] [UniVITrainer] [INFO] [Epoch 100] Val loss=900.9474 (beta=1.250, gamma=4.350)
Training UniVI:  25%|██▍       | 99/400 [2:11:39<6:19:19, 75.61s/it, beta=1.250, gamma=3.107, train_loss=906.9410, val_loss=900.9474]
Training UniVI:  25%|██▌       | 100/400 [2:11:39<6:18:11, 75.64s/it, beta=1.250, gamma=3.107, train_loss=906.9410, val_loss=900.9474]
Training UniVI:  25%|██▌       | 100/400 [2:12:55<6:18:11, 75.64s/it, beta=1.250, gamma=3.231, train_loss=908.7209, val_loss=900.0812]
Training UniVI:  25%|██▌       | 101/400 [2:12:55<6:16:38, 75.58s/it, beta=1.250, gamma=3.231, train_loss=908.7209, val_loss=900.0812]
Training UniVI:  25%|██▌       | 101/400 [2:14:10<6:16:38, 75.58s/it, beta=1.250, gamma=3.356, train_loss=909.3667, val_loss=899.4149]
Training UniVI:  26%|██▌       | 102/400 [2:14:10<6:15:12, 75.54s/it, beta=1.250, gamma=3.356, train_loss=909.3667, val_loss=899.4149]
Training UniVI:  26%|██▌       | 102/400 [2:15:25<6:15:12, 75.54s/it, beta=1.250, gamma=3.480, train_loss=907.9082, val_loss=900.0158]
Training UniVI:  26%|██▌       | 103/400 [2:15:25<6:13:20, 75.42s/it, beta=1.250, gamma=3.480, train_loss=907.9082, val_loss=900.0158]
Training UniVI:  26%|██▌       | 103/400 [2:16:40<6:13:20, 75.42s/it, beta=1.250, gamma=3.604, train_loss=907.4508, val_loss=898.8228]
Training UniVI:  26%|██▌       | 104/400 [2:16:40<6:11:57, 75.40s/it, beta=1.250, gamma=3.604, train_loss=907.4508, val_loss=898.8228]
Training UniVI:  26%|██▌       | 104/400 [2:17:56<6:11:57, 75.40s/it, beta=1.250, gamma=3.729, train_loss=906.7520, val_loss=899.8442]
Training UniVI:  26%|██▋       | 105/400 [2:17:56<6:10:44, 75.40s/it, beta=1.250, gamma=3.729, train_loss=906.7520, val_loss=899.8442]
Training UniVI:  26%|██▋       | 105/400 [2:19:12<6:10:44, 75.40s/it, beta=1.250, gamma=3.853, train_loss=905.1335, val_loss=898.8598]
Training UniVI:  26%|██▋       | 106/400 [2:19:12<6:09:47, 75.47s/it, beta=1.250, gamma=3.853, train_loss=905.1335, val_loss=898.8598]
Training UniVI:  26%|██▋       | 106/400 [2:20:27<6:09:47, 75.47s/it, beta=1.250, gamma=3.977, train_loss=907.4879, val_loss=899.3624]
Training UniVI:  27%|██▋       | 107/400 [2:20:27<6:08:22, 75.44s/it, beta=1.250, gamma=3.977, train_loss=907.4879, val_loss=899.3624]
Training UniVI:  27%|██▋       | 107/400 [2:21:42<6:08:22, 75.44s/it, beta=1.250, gamma=4.101, train_loss=907.8056, val_loss=898.8742]
Training UniVI:  27%|██▋       | 108/400 [2:21:42<6:07:18, 75.47s/it, beta=1.250, gamma=4.101, train_loss=907.8056, val_loss=898.8742]
Training UniVI:  27%|██▋       | 108/400 [2:22:57<6:07:18, 75.47s/it, beta=1.250, gamma=4.226, train_loss=908.0688, val_loss=900.4546]
[2026-09-20 23:34:20,001] [UniVITrainer] [INFO] [Epoch 109] Best tracking warmup ends next epoch (best_epoch_warmup=110).
Training UniVI:  27%|██▋       | 109/400 [2:22:57<6:05:18, 75.32s/it, beta=1.250, gamma=4.226, train_loss=908.0688, val_loss=900.4546]
Training UniVI:  27%|██▋       | 109/400 [2:24:14<6:05:18, 75.32s/it, beta=1.250, gamma=4.350, train_loss=905.3979, val_loss=898.0684]
[2026-09-20 23:35:36,336] [UniVITrainer] [INFO] [Epoch 110] New best val loss: 898.0684
Training UniVI:  28%|██▊       | 110/400 [2:24:14<6:05:31, 75.63s/it, beta=1.250, gamma=4.350, train_loss=905.3979, val_loss=898.0684]
Training UniVI:  28%|██▊       | 110/400 [2:25:29<6:05:31, 75.63s/it, beta=1.250, gamma=4.350, train_loss=907.7376, val_loss=898.1242]
Training UniVI:  28%|██▊       | 111/400 [2:25:29<6:04:14, 75.62s/it, beta=1.250, gamma=4.350, train_loss=907.7376, val_loss=898.1242]
Training UniVI:  28%|██▊       | 111/400 [2:26:45<6:04:14, 75.62s/it, beta=1.250, gamma=4.350, train_loss=905.0269, val_loss=899.0615]
Training UniVI:  28%|██▊       | 112/400 [2:26:45<6:02:47, 75.58s/it, beta=1.250, gamma=4.350, train_loss=905.0269, val_loss=899.0615]
Training UniVI:  28%|██▊       | 112/400 [2:28:01<6:02:47, 75.58s/it, beta=1.250, gamma=4.350, train_loss=906.3825, val_loss=897.5375]
[2026-09-20 23:39:23,087] [UniVITrainer] [INFO] [Epoch 113] New best val loss: 897.5375
Training UniVI:  28%|██▊       | 113/400 [2:28:01<6:01:37, 75.60s/it, beta=1.250, gamma=4.350, train_loss=906.3825, val_loss=897.5375]
Training UniVI:  28%|██▊       | 113/400 [2:29:16<6:01:37, 75.60s/it, beta=1.250, gamma=4.350, train_loss=905.1246, val_loss=898.2026]
Training UniVI:  28%|██▊       | 114/400 [2:29:16<6:00:21, 75.60s/it, beta=1.250, gamma=4.350, train_loss=905.1246, val_loss=898.2026]
Training UniVI:  28%|██▊       | 114/400 [2:30:32<6:00:21, 75.60s/it, beta=1.250, gamma=4.350, train_loss=904.0457, val_loss=898.3394]
Training UniVI:  29%|██▉       | 115/400 [2:30:32<5:59:02, 75.59s/it, beta=1.250, gamma=4.350, train_loss=904.0457, val_loss=898.3394]
Training UniVI:  29%|██▉       | 115/400 [2:31:47<5:59:02, 75.59s/it, beta=1.250, gamma=4.350, train_loss=905.7235, val_loss=898.1236]
Training UniVI:  29%|██▉       | 116/400 [2:31:47<5:57:42, 75.57s/it, beta=1.250, gamma=4.350, train_loss=905.7235, val_loss=898.1236]
Training UniVI:  29%|██▉       | 116/400 [2:33:03<5:57:42, 75.57s/it, beta=1.250, gamma=4.350, train_loss=905.1421, val_loss=898.4047]
Training UniVI:  29%|██▉       | 117/400 [2:33:03<5:56:18, 75.54s/it, beta=1.250, gamma=4.350, train_loss=905.1421, val_loss=898.4047]
Training UniVI:  29%|██▉       | 117/400 [2:34:18<5:56:18, 75.54s/it, beta=1.250, gamma=4.350, train_loss=903.2056, val_loss=900.2105]
Training UniVI:  30%|██▉       | 118/400 [2:34:18<5:55:02, 75.54s/it, beta=1.250, gamma=4.350, train_loss=903.2056, val_loss=900.2105]
Training UniVI:  30%|██▉       | 118/400 [2:35:33<5:55:02, 75.54s/it, beta=1.250, gamma=4.350, train_loss=903.6929, val_loss=898.9586]
Training UniVI:  30%|██▉       | 119/400 [2:35:33<5:53:22, 75.45s/it, beta=1.250, gamma=4.350, train_loss=903.6929, val_loss=898.9586]
Training UniVI:  30%|██▉       | 119/400 [2:36:49<5:53:22, 75.45s/it, beta=1.250, gamma=4.350, train_loss=902.5393, val_loss=899.2333]
Training UniVI:  30%|███       | 120/400 [2:36:49<5:52:11, 75.47s/it, beta=1.250, gamma=4.350, train_loss=902.5393, val_loss=899.2333]
Training UniVI:  30%|███       | 120/400 [2:38:04<5:52:11, 75.47s/it, beta=1.250, gamma=4.350, train_loss=903.6319, val_loss=899.1731]
Training UniVI:  30%|███       | 121/400 [2:38:04<5:50:51, 75.45s/it, beta=1.250, gamma=4.350, train_loss=903.6319, val_loss=899.1731]
Training UniVI:  30%|███       | 121/400 [2:39:20<5:50:51, 75.45s/it, beta=1.250, gamma=4.350, train_loss=904.0176, val_loss=898.9078]
Training UniVI:  30%|███       | 122/400 [2:39:20<5:49:42, 75.48s/it, beta=1.250, gamma=4.350, train_loss=904.0176, val_loss=898.9078]
Training UniVI:  30%|███       | 122/400 [2:40:35<5:49:42, 75.48s/it, beta=1.250, gamma=4.350, train_loss=901.0650, val_loss=898.9720]
Training UniVI:  31%|███       | 123/400 [2:40:35<5:48:19, 75.45s/it, beta=1.250, gamma=4.350, train_loss=901.0650, val_loss=898.9720]
Training UniVI:  31%|███       | 123/400 [2:41:51<5:48:19, 75.45s/it, beta=1.250, gamma=4.350, train_loss=903.6169, val_loss=900.1036]
Training UniVI:  31%|███       | 124/400 [2:41:51<5:47:06, 75.46s/it, beta=1.250, gamma=4.350, train_loss=903.6169, val_loss=900.1036]
Training UniVI:  31%|███       | 124/400 [2:43:06<5:47:06, 75.46s/it, beta=1.250, gamma=4.350, train_loss=900.1662, val_loss=898.3458]
Training UniVI:  31%|███▏      | 125/400 [2:43:06<5:45:48, 75.45s/it, beta=1.250, gamma=4.350, train_loss=900.1662, val_loss=898.3458]
Training UniVI:  31%|███▏      | 125/400 [2:44:24<5:45:48, 75.45s/it, beta=1.250, gamma=4.350, train_loss=901.7313, val_loss=899.3147]
Training UniVI:  32%|███▏      | 126/400 [2:44:24<5:48:10, 76.24s/it, beta=1.250, gamma=4.350, train_loss=901.7313, val_loss=899.3147]
Training UniVI:  32%|███▏      | 126/400 [2:45:40<5:48:10, 76.24s/it, beta=1.250, gamma=4.350, train_loss=902.7179, val_loss=897.8453]
Training UniVI:  32%|███▏      | 127/400 [2:45:40<5:45:43, 75.98s/it, beta=1.250, gamma=4.350, train_loss=902.7179, val_loss=897.8453]
Training UniVI:  32%|███▏      | 127/400 [2:46:55<5:45:43, 75.98s/it, beta=1.250, gamma=4.350, train_loss=902.0468, val_loss=899.1118]
Training UniVI:  32%|███▏      | 128/400 [2:46:55<5:43:22, 75.74s/it, beta=1.250, gamma=4.350, train_loss=902.0468, val_loss=899.1118]
Training UniVI:  32%|███▏      | 128/400 [2:48:10<5:43:22, 75.74s/it, beta=1.250, gamma=4.350, train_loss=902.8791, val_loss=897.7847]
Training UniVI:  32%|███▏      | 129/400 [2:48:10<5:41:43, 75.66s/it, beta=1.250, gamma=4.350, train_loss=902.8791, val_loss=897.7847]
Training UniVI:  32%|███▏      | 129/400 [2:49:26<5:41:43, 75.66s/it, beta=1.250, gamma=4.350, train_loss=902.0185, val_loss=898.4225]
Training UniVI:  32%|███▎      | 130/400 [2:49:26<5:40:23, 75.64s/it, beta=1.250, gamma=4.350, train_loss=902.0185, val_loss=898.4225]
Training UniVI:  32%|███▎      | 130/400 [2:50:41<5:40:23, 75.64s/it, beta=1.250, gamma=4.350, train_loss=902.4700, val_loss=899.0886]
Training UniVI:  33%|███▎      | 131/400 [2:50:41<5:38:56, 75.60s/it, beta=1.250, gamma=4.350, train_loss=902.4700, val_loss=899.0886]
Training UniVI:  33%|███▎      | 131/400 [2:51:57<5:38:56, 75.60s/it, beta=1.250, gamma=4.350, train_loss=901.0629, val_loss=899.4307]
Training UniVI:  33%|███▎      | 132/400 [2:51:57<5:37:10, 75.49s/it, beta=1.250, gamma=4.350, train_loss=901.0629, val_loss=899.4307]
Training UniVI:  33%|███▎      | 132/400 [2:53:12<5:37:10, 75.49s/it, beta=1.250, gamma=4.350, train_loss=903.1616, val_loss=899.1182]
Training UniVI:  33%|███▎      | 133/400 [2:53:12<5:35:56, 75.49s/it, beta=1.250, gamma=4.350, train_loss=903.1616, val_loss=899.1182]
Training UniVI:  33%|███▎      | 133/400 [2:54:28<5:35:56, 75.49s/it, beta=1.250, gamma=4.350, train_loss=900.4715, val_loss=899.2144]
Training UniVI:  34%|███▎      | 134/400 [2:54:28<5:34:32, 75.46s/it, beta=1.250, gamma=4.350, train_loss=900.4715, val_loss=899.2144]
Training UniVI:  34%|███▎      | 134/400 [2:55:43<5:34:32, 75.46s/it, beta=1.250, gamma=4.350, train_loss=900.5375, val_loss=898.5563]
Training UniVI:  34%|███▍      | 135/400 [2:55:43<5:33:53, 75.60s/it, beta=1.250, gamma=4.350, train_loss=900.5375, val_loss=898.5563]
Training UniVI:  34%|███▍      | 135/400 [2:56:59<5:33:53, 75.60s/it, beta=1.250, gamma=4.350, train_loss=900.9435, val_loss=898.1248]
Training UniVI:  34%|███▍      | 136/400 [2:56:59<5:32:58, 75.68s/it, beta=1.250, gamma=4.350, train_loss=900.9435, val_loss=898.1248]
Training UniVI:  34%|███▍      | 136/400 [2:58:15<5:32:58, 75.68s/it, beta=1.250, gamma=4.350, train_loss=902.1789, val_loss=900.5460]
Training UniVI:  34%|███▍      | 137/400 [2:58:15<5:31:36, 75.65s/it, beta=1.250, gamma=4.350, train_loss=902.1789, val_loss=900.5460]
Training UniVI:  34%|███▍      | 137/400 [2:59:30<5:31:36, 75.65s/it, beta=1.250, gamma=4.350, train_loss=899.7248, val_loss=900.1946]
Training UniVI:  34%|███▍      | 138/400 [2:59:30<5:30:04, 75.59s/it, beta=1.250, gamma=4.350, train_loss=899.7248, val_loss=900.1946]
Training UniVI:  34%|███▍      | 138/400 [3:00:46<5:30:04, 75.59s/it, beta=1.250, gamma=4.350, train_loss=900.4848, val_loss=898.8304]
Training UniVI:  35%|███▍      | 139/400 [3:00:46<5:28:29, 75.51s/it, beta=1.250, gamma=4.350, train_loss=900.4848, val_loss=898.8304]
Training UniVI:  35%|███▍      | 139/400 [3:02:02<5:28:29, 75.51s/it, beta=1.250, gamma=4.350, train_loss=899.1465, val_loss=899.4390]
Training UniVI:  35%|███▌      | 140/400 [3:02:02<5:27:40, 75.62s/it, beta=1.250, gamma=4.350, train_loss=899.1465, val_loss=899.4390]
Training UniVI:  35%|███▌      | 140/400 [3:03:17<5:27:40, 75.62s/it, beta=1.250, gamma=4.350, train_loss=898.7263, val_loss=899.2847]
Training UniVI:  35%|███▌      | 141/400 [3:03:17<5:26:23, 75.61s/it, beta=1.250, gamma=4.350, train_loss=898.7263, val_loss=899.2847]
Training UniVI:  35%|███▌      | 141/400 [3:04:33<5:26:23, 75.61s/it, beta=1.250, gamma=4.350, train_loss=899.1082, val_loss=898.0203]
Training UniVI:  36%|███▌      | 142/400 [3:04:33<5:25:04, 75.60s/it, beta=1.250, gamma=4.350, train_loss=899.1082, val_loss=898.0203]
Training UniVI:  36%|███▌      | 142/400 [3:05:48<5:25:04, 75.60s/it, beta=1.250, gamma=4.350, train_loss=899.0443, val_loss=898.5793]
Training UniVI:  36%|███▌      | 143/400 [3:05:48<5:23:50, 75.60s/it, beta=1.250, gamma=4.350, train_loss=899.0443, val_loss=898.5793]
Training UniVI:  36%|███▌      | 143/400 [3:07:04<5:23:50, 75.60s/it, beta=1.250, gamma=4.350, train_loss=898.5953, val_loss=899.5089]
Training UniVI:  36%|███▌      | 144/400 [3:07:04<5:22:56, 75.69s/it, beta=1.250, gamma=4.350, train_loss=898.5953, val_loss=899.5089]
Training UniVI:  36%|███▌      | 144/400 [3:08:20<5:22:56, 75.69s/it, beta=1.250, gamma=4.350, train_loss=899.9576, val_loss=898.4056]
Training UniVI:  36%|███▋      | 145/400 [3:08:20<5:21:17, 75.60s/it, beta=1.250, gamma=4.350, train_loss=899.9576, val_loss=898.4056]
Training UniVI:  36%|███▋      | 145/400 [3:09:35<5:21:17, 75.60s/it, beta=1.250, gamma=4.350, train_loss=898.0060, val_loss=898.8638]
Training UniVI:  36%|███▋      | 146/400 [3:09:35<5:19:47, 75.54s/it, beta=1.250, gamma=4.350, train_loss=898.0060, val_loss=898.8638]
Training UniVI:  36%|███▋      | 146/400 [3:10:50<5:19:47, 75.54s/it, beta=1.250, gamma=4.350, train_loss=897.7063, val_loss=899.0373]
Training UniVI:  37%|███▋      | 147/400 [3:10:50<5:18:19, 75.49s/it, beta=1.250, gamma=4.350, train_loss=897.7063, val_loss=899.0373]
Training UniVI:  37%|███▋      | 147/400 [3:12:06<5:18:19, 75.49s/it, beta=1.250, gamma=4.350, train_loss=898.3617, val_loss=898.3118]
Training UniVI:  37%|███▋      | 148/400 [3:12:06<5:17:05, 75.50s/it, beta=1.250, gamma=4.350, train_loss=898.3617, val_loss=898.3118]
Training UniVI:  37%|███▋      | 148/400 [3:13:21<5:17:05, 75.50s/it, beta=1.250, gamma=4.350, train_loss=897.7690, val_loss=899.1686]
Training UniVI:  37%|███▋      | 149/400 [3:13:21<5:15:33, 75.43s/it, beta=1.250, gamma=4.350, train_loss=897.7690, val_loss=899.1686]
[2026-09-21 00:25:58,193] [UniVITrainer] [INFO] [Epoch 150] Train loss=897.3620 (beta=1.250, gamma=4.350)
[2026-09-21 00:25:59,229] [UniVITrainer] [INFO] [Epoch 150] Val loss=900.1693 (beta=1.250, gamma=4.350)
Training UniVI:  37%|███▋      | 149/400 [3:14:37<5:15:33, 75.43s/it, beta=1.250, gamma=4.350, train_loss=897.3620, val_loss=900.1693]
Training UniVI:  38%|███▊      | 150/400 [3:14:37<5:14:19, 75.44s/it, beta=1.250, gamma=4.350, train_loss=897.3620, val_loss=900.1693]
Training UniVI:  38%|███▊      | 150/400 [3:15:52<5:14:19, 75.44s/it, beta=1.250, gamma=4.350, train_loss=896.5088, val_loss=899.1574]
Training UniVI:  38%|███▊      | 151/400 [3:15:52<5:13:02, 75.43s/it, beta=1.250, gamma=4.350, train_loss=896.5088, val_loss=899.1574]
Training UniVI:  38%|███▊      | 151/400 [3:17:08<5:13:02, 75.43s/it, beta=1.250, gamma=4.350, train_loss=899.3954, val_loss=898.3231]
Training UniVI:  38%|███▊      | 152/400 [3:17:08<5:11:57, 75.47s/it, beta=1.250, gamma=4.350, train_loss=899.3954, val_loss=898.3231]
Training UniVI:  38%|███▊      | 152/400 [3:18:23<5:11:57, 75.47s/it, beta=1.250, gamma=4.350, train_loss=898.9626, val_loss=902.7683]
Training UniVI:  38%|███▊      | 153/400 [3:18:23<5:10:45, 75.49s/it, beta=1.250, gamma=4.350, train_loss=898.9626, val_loss=902.7683]
Training UniVI:  38%|███▊      | 153/400 [3:19:39<5:10:45, 75.49s/it, beta=1.250, gamma=4.350, train_loss=896.4636, val_loss=899.4675]
Training UniVI:  38%|███▊      | 154/400 [3:19:39<5:09:36, 75.51s/it, beta=1.250, gamma=4.350, train_loss=896.4636, val_loss=899.4675]
Training UniVI:  38%|███▊      | 154/400 [3:20:57<5:09:36, 75.51s/it, beta=1.250, gamma=4.350, train_loss=897.9316, val_loss=898.8793]
Training UniVI:  39%|███▉      | 155/400 [3:20:57<5:11:11, 76.21s/it, beta=1.250, gamma=4.350, train_loss=897.9316, val_loss=898.8793]
Training UniVI:  39%|███▉      | 155/400 [3:22:12<5:11:11, 76.21s/it, beta=1.250, gamma=4.350, train_loss=895.8016, val_loss=897.9469]
Training UniVI:  39%|███▉      | 156/400 [3:22:12<5:09:31, 76.11s/it, beta=1.250, gamma=4.350, train_loss=895.8016, val_loss=897.9469]
Training UniVI:  39%|███▉      | 156/400 [3:23:28<5:09:31, 76.11s/it, beta=1.250, gamma=4.350, train_loss=897.0851, val_loss=900.0189]
Training UniVI:  39%|███▉      | 157/400 [3:23:28<5:07:36, 75.95s/it, beta=1.250, gamma=4.350, train_loss=897.0851, val_loss=900.0189]
Training UniVI:  39%|███▉      | 157/400 [3:24:44<5:07:36, 75.95s/it, beta=1.250, gamma=4.350, train_loss=898.0113, val_loss=899.2750]
Training UniVI:  40%|███▉      | 158/400 [3:24:44<5:06:41, 76.04s/it, beta=1.250, gamma=4.350, train_loss=898.0113, val_loss=899.2750]
Training UniVI:  40%|███▉      | 158/400 [3:26:00<5:06:41, 76.04s/it, beta=1.250, gamma=4.350, train_loss=897.4514, val_loss=900.2494]
Training UniVI:  40%|███▉      | 159/400 [3:26:00<5:05:36, 76.09s/it, beta=1.250, gamma=4.350, train_loss=897.4514, val_loss=900.2494]
Training UniVI:  40%|███▉      | 159/400 [3:27:16<5:05:36, 76.09s/it, beta=1.250, gamma=4.350, train_loss=896.6251, val_loss=899.7239]
Training UniVI:  40%|████      | 160/400 [3:27:16<5:03:44, 75.93s/it, beta=1.250, gamma=4.350, train_loss=896.6251, val_loss=899.7239]
Training UniVI:  40%|████      | 160/400 [3:28:32<5:03:44, 75.93s/it, beta=1.250, gamma=4.350, train_loss=895.9001, val_loss=901.7901]
Training UniVI:  40%|████      | 161/400 [3:28:32<5:02:08, 75.85s/it, beta=1.250, gamma=4.350, train_loss=895.9001, val_loss=901.7901]
Training UniVI:  40%|████      | 161/400 [3:29:48<5:02:08, 75.85s/it, beta=1.250, gamma=4.350, train_loss=895.3164, val_loss=899.7950]
Training UniVI:  40%|████      | 162/400 [3:29:48<5:01:04, 75.90s/it, beta=1.250, gamma=4.350, train_loss=895.3164, val_loss=899.7950]
Training UniVI:  40%|████      | 162/400 [3:31:04<5:01:04, 75.90s/it, beta=1.250, gamma=4.350, train_loss=894.6737, val_loss=899.8857]
[2026-09-21 00:42:26,325] [UniVITrainer] [INFO] Early stopping at epoch 163 (best val loss=897.5375, best epoch=113)
Training UniVI:  40%|████      | 162/400 [3:31:04<5:10:05, 78.17s/it, beta=1.250, gamma=4.350, train_loss=894.6737, val_loss=899.8857]

[2026-09-21 00:42:26,359] [UniVITrainer] [INFO] Restored best model from epoch 113 (val loss=897.5375)

Preprocess and embed the queries#

Queries go through the reference’s fitted preprocessors, then through the encoder of the one modality they have.

q_rna = rna_prep.transform(rna[rna_only_idx])
q_atac = atac_prep.transform(atac[atac_only_idx])
z_q_rna = encode_adata(model, q_rna, modality="rna", device=device, latent="modality_mean")
z_q_atac = encode_adata(model, q_atac, modality="atac", device=device, latent="modality_mean")

# The reference itself is represented by its fused (RNA + ATAC) embedding.
z_ref = encode_fused_adata_pair(model, adata_by_mod=ref, device=device, write_to_adatas=False)["mu"]
joint = stack_embeddings(model, [("reference", "rna", ref["rna"]), ("query", "rna", q_rna),
                                 ("query", "atac", q_atac)], device=device)
sc.pp.neighbors(joint, use_rep="X_univi", n_neighbors=30)
sc.tl.umap(joint, random_state=0)
sc.pl.umap(joint, color=["block", "cell_type"], wspace=0.45, legend_fontsize=7)
../_images/346388448a200e31e54a95f045f1d8eca81e69ad3c3f889c8c31199ab7eb5fd4.png

Transfer labels with a confidence score#

A k-nearest-neighbor vote in latent space assigns each query cell the most common reference label among its neighbors. The fraction of neighbors that agree is a simple confidence score, and the mean distance to those neighbors indicates how well the reference covers the cell.

knn = KNeighborsClassifier(n_neighbors=15).fit(z_ref, ref["rna"].obs["cell_type"].astype(str))

def transfer(z):
    proba = knn.predict_proba(z)
    dist = knn.kneighbors(z)[0].mean(axis=1)
    return pd.DataFrame({"predicted": knn.classes_[proba.argmax(1)], "confidence": proba.max(1), "distance": dist})

results = {}
for name, z, cells in [("RNA-only", z_q_rna, q_rna), ("ATAC-only", z_q_atac, q_atac)]:
    df = transfer(z)
    df["true"] = cells.obs["cell_type"].astype(str).to_numpy()
    results[name] = df
    seen = df["true"] != HOLDOUT_TYPE
    print(f"{name}: accuracy on cell types present in the reference = {(df.predicted == df.true)[seen].mean():.3f}")
RNA-only: accuracy on cell types present in the reference = 0.933
ATAC-only: accuracy on cell types present in the reference = 0.924
df = results["ATAC-only"]
order = sorted(set(df["true"]) | set(df["predicted"]))
cm = confusion_matrix(df["true"], df["predicted"], labels=order)
plot_confusion_matrix(cm, labels=order, normalize="true", title="ATAC-only query: true vs transferred label")
../_images/03caba7c271d86a3f8b808b2a76dc69991da91864c6de63f368425fce9cad818.png

The held-out cell type cannot receive its own label (the reference has never seen it), so it is assigned to its nearest relative. Its confidence and distance scores are what give it away. Thresholds on these scores are a practical way to flag cells for review:

fig, axes = plt.subplots(1, 2, figsize=(9, 3.2))
for ax, col in zip(axes, ["confidence", "distance"]):
    for name, df in results.items():
        is_new = df["true"] == HOLDOUT_TYPE
        ax.hist(df.loc[~is_new, col], bins=30, alpha=0.5, density=True, label=f"{name}: seen types")
        ax.hist(df.loc[is_new, col], bins=30, alpha=0.7, density=True, histtype="step", lw=2,
                label=f"{name}: {HOLDOUT_TYPE}")
    ax.set(xlabel=col, ylabel="density")
axes[0].legend(fontsize=7, frameon=False)
plt.tight_layout()
plt.show()
../_images/a43a2fa26ca9953353fb03792346c455a1e8b1b7aeba1e557fb00e3c44ae4650.png

Predict the missing modality#

For the ATAC-only cohort, decoding RNA from the ATAC embedding predicts gene expression. Because these cells were actually measured with RNA too, we can check the prediction.

predicted_rna = cross_modal_predict(model, q_atac, src_mod="atac", tgt_mod="rna", device=device)
truth = rna_prep.transform(rna[atac_only_idx])            # hidden RNA of the same cells
r = pd.Series(pearson_corr_per_feature(np.asarray(truth.X), predicted_rna), index=truth.var_names)
print(f"ATAC to RNA, median per-gene Pearson r = {r.median():.3f}")
r.sort_values(ascending=False).head(10).round(3)
ATAC to RNA, median per-gene Pearson r = 0.228
genes
FCRL1     0.927
PAX5      0.923
MS4A1     0.906
SLC8A1    0.903
EBF1      0.894
LYN       0.889
ZEB2      0.887
PLXDC2    0.887
LRMDA     0.881
LEF1      0.875
dtype: float32

Using your own query data#

The preprocessors refuse queries that lack reference features, rather than silently filling them. Check coverage first:

missing = pd.Index(rna_prep.features_).difference(my_query.var_names)
print(len(missing), "reference genes missing from the query")
  • RNA: match gene identifiers (symbols vs Ensembl IDs, and the annotation version) before transforming. If a few genes are missing, the cleanest fix is to refit the reference on the genes both datasets share. Zero-filling missing genes makes them look unexpressed and biases the embedding.

  • ATAC: query accessibility must be counted on the reference peak set (for example, re-quantify query fragments over the reference peaks with Signac’s FeatureMatrix or SnapATAC2). Peaks called separately on the query are not interchangeable, even if their number matches.

  • Batch effects: a query from a different lab or chemistry may land slightly offset from the reference. Label transfer is often still robust; the confidence and distance scores above help you judge. For supervised adaptation, see Cell-type heads and refinement.