def eval_metrics_query_and_gallery_dataset(
    model, query_dataloader, gallery_dataloader, device, k_vals, opt
):
    """
    Compute evaluation metrics on test-dataset, e.g. NMI, F1 and Recall @ k.

    Args:
        model:               PyTorch network, network to compute evaluation metrics for.
        query_dataloader:    PyTorch Dataloader, dataloader for query dataset, for which nearest neighbours in the gallery dataset are retrieved.
        gallery_dataloader:  PyTorch Dataloader, dataloader for gallery dataset, provides target samples which are to be retrieved in correspondance to the query dataset.
        device:              torch.device, Device to run inference on.
        k_vals:              list of int, Recall values to compute
        opt:                 argparse.Namespace, contains all training-specific parameters.
    Returns:
        F1 score (float), NMI score (float), recall_at_ks (list of float), query data embedding (np.ndarray), gallery data embedding (np.ndarray)
    """
    torch.cuda.empty_cache()

    _ = model.eval()
    n_classes = len(query_dataloader.dataset.avail_classes)

    with torch.no_grad():
        ### For all query test images, extract features
        query_target_labels, query_feature_coll = [], []
        query_image_paths = [x[0] for x in query_dataloader.dataset.image_list]
        query_iter = tqdm(query_dataloader, desc="Extraction Query Features")
        for idx, inp in enumerate(query_iter):
            input_img, target = inp[-1], inp[0]
            query_target_labels.extend(target.numpy().tolist())
            out = model(input_img.to(device), feature=True)
            query_feature_coll.extend(out.cpu().detach().numpy().tolist())

        ### For all gallery test images, extract features
        gallery_target_labels, gallery_feature_coll = [], []
        gallery_image_paths = [
            x[0] for x in gallery_dataloader.dataset.image_list
        ]
        gallery_iter = tqdm(
            gallery_dataloader, desc="Extraction Gallery Features"
        )
        for idx, inp in enumerate(gallery_iter):
            input_img, target = inp[-1], inp[0]
            gallery_target_labels.extend(target.numpy().tolist())
            out = model(input_img.to(device), feature=True)
            gallery_feature_coll.extend(out.cpu().detach().numpy().tolist())

        query_target_labels, query_feature_coll = np.hstack(
            query_target_labels
        ).reshape(-1, 1), np.vstack(query_feature_coll).astype("float32")
        gallery_target_labels, gallery_feature_coll = np.hstack(
            gallery_target_labels
        ).reshape(-1, 1), np.vstack(gallery_feature_coll).astype("float32")

        torch.cuda.empty_cache()

        ### Set CPU Cluster index
        stackset = np.concatenate(
            [query_feature_coll, gallery_feature_coll], axis=0
        )
        stacklabels = np.concatenate(
            [query_target_labels, gallery_target_labels], axis=0
        )
        cpu_cluster_index = faiss.IndexFlatL2(stackset.shape[-1])
        kmeans = faiss.Clustering(stackset.shape[-1], n_classes)
        kmeans.niter = 20
        kmeans.min_points_per_centroid = 1
        kmeans.max_points_per_centroid = 1000000000

        ### Train Kmeans
        kmeans.train(stackset, cpu_cluster_index)
        computed_centroids = faiss.vector_float_to_array(
            kmeans.centroids
        ).reshape(n_classes, stackset.shape[-1])

        ### Assign feature points to clusters
        faiss_search_index = faiss.IndexFlatL2(computed_centroids.shape[-1])
        faiss_search_index.add(computed_centroids)
        _, model_generated_cluster_labels = faiss_search_index.search(
            stackset, 1
        )

        ### Compute NMI
        NMI = metrics.cluster.normalized_mutual_info_score(
            model_generated_cluster_labels.reshape(-1), stacklabels.reshape(-1)
        )

        ### Recover max(k_vals) nearest neighbours to use for recall computation
        faiss_search_index = faiss.IndexFlatL2(gallery_feature_coll.shape[-1])
        faiss_search_index.add(gallery_feature_coll)
        _, k_closest_points = faiss_search_index.search(
            query_feature_coll, int(np.max(k_vals))
        )
        k_closest_classes = gallery_target_labels.reshape(-1)[k_closest_points]

        ### Compute Recall
        recall_all_k = []
        for k in k_vals:
            recall_at_k = np.sum(
                [
                    1
                    for target, recalled_predictions in zip(
                        query_target_labels, k_closest_classes
                    )
                    if target in recalled_predictions[:k]
                ]
            ) / len(query_target_labels)
            recall_all_k.append(recall_at_k)
        recall_str = ", ".join(
            "@{0}: {1:.4f}".format(k, rec)
            for k, rec in zip(k_vals, recall_all_k)
        )

        ### Compute F1 score
        F1 = f1_score(
            model_generated_cluster_labels,
            stacklabels,
            stackset,
            computed_centroids,
        )

    return F1, NMI, recall_all_k, query_feature_coll, gallery_feature_coll

    Compute evaluation metrics on test-dataset, e.g. NMI, F1 and Recall @ k.

    Args:
        model:               PyTorch network, network to compute evaluation metrics for.
        query_dataloader:    PyTorch Dataloader, dataloader for query dataset, for which nearest neighbours in the gallery dataset are retrieved.
        gallery_dataloader:  PyTorch Dataloader, dataloader for gallery dataset, provides target samples which are to be retrieved in correspondance to the query dataset.
        device:              torch.device, Device to run inference on.
        k_vals:              list of int, Recall values to compute
        opt:                 argparse.Namespace, contains all training-specific parameters.
    Returns:
        F1 score (float), NMI score (float), recall_at_ks (list of float), query data embedding (np.ndarray), gallery data embedding (np.ndarray)
    
examples/pytorch/hilander/PSS/Smooth_AP/src/auxiliaries.py