precisionai-agrieval 0.1.0.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. precisionai/__init__.py +2 -0
  2. precisionai/agrieval/__init__.py +2 -0
  3. precisionai/agrieval/api/__init__.py +2 -0
  4. precisionai/agrieval/api/app.py +103 -0
  5. precisionai/agrieval/api/config.py +31 -0
  6. precisionai/agrieval/emb/__init__.py +28 -0
  7. precisionai/agrieval/emb/api/__init__.py +2 -0
  8. precisionai/agrieval/emb/api/app.py +114 -0
  9. precisionai/agrieval/emb/api/config.py +8 -0
  10. precisionai/agrieval/emb/api/routes/__init__.py +2 -0
  11. precisionai/agrieval/emb/api/routes/evaluate.py +126 -0
  12. precisionai/agrieval/emb/metrics/__init__.py +115 -0
  13. precisionai/agrieval/emb/metrics/_utils.py +240 -0
  14. precisionai/agrieval/emb/metrics/analysis.py +202 -0
  15. precisionai/agrieval/emb/metrics/cross_model.py +235 -0
  16. precisionai/agrieval/emb/metrics/duplicates.py +190 -0
  17. precisionai/agrieval/emb/metrics/geometry.py +282 -0
  18. precisionai/agrieval/emb/metrics/label_aware.py +805 -0
  19. precisionai/agrieval/emb/metrics/neighbors.py +195 -0
  20. precisionai/agrieval/emb/metrics/ranking.py +258 -0
  21. precisionai/agrieval/emb/metrics/similarity.py +212 -0
  22. precisionai/agrieval/emb/schemas/__init__.py +14 -0
  23. precisionai/agrieval/emb/schemas/evaluate.py +471 -0
  24. precisionai/agrieval/emb/services/__init__.py +2 -0
  25. precisionai/agrieval/emb/services/evaluate.py +1117 -0
  26. precisionai/agrieval/emb/services/reporting.py +754 -0
  27. precisionai/agrieval/seg/__init__.py +9 -0
  28. precisionai/agrieval/seg/api/__init__.py +2 -0
  29. precisionai/agrieval/seg/api/config.py +8 -0
  30. precisionai/agrieval/seg/api/routes/__init__.py +2 -0
  31. precisionai/agrieval/seg/api/routes/evaluate.py +75 -0
  32. precisionai/agrieval/seg/cli.py +73 -0
  33. precisionai/agrieval/seg/metrics/__init__.py +22 -0
  34. precisionai/agrieval/seg/metrics/segmentation.py +165 -0
  35. precisionai/agrieval/seg/schemas/__init__.py +2 -0
  36. precisionai/agrieval/seg/schemas/evaluate.py +129 -0
  37. precisionai/agrieval/seg/services/__init__.py +2 -0
  38. precisionai/agrieval/seg/services/evaluate.py +573 -0
  39. precisionai_agrieval-0.1.0.dev0.dist-info/METADATA +441 -0
  40. precisionai_agrieval-0.1.0.dev0.dist-info/RECORD +46 -0
  41. precisionai_agrieval-0.1.0.dev0.dist-info/WHEEL +5 -0
  42. precisionai_agrieval-0.1.0.dev0.dist-info/entry_points.txt +4 -0
  43. precisionai_agrieval-0.1.0.dev0.dist-info/licenses/LICENSE.md +201 -0
  44. precisionai_agrieval-0.1.0.dev0.dist-info/scm_file_list.json +201 -0
  45. precisionai_agrieval-0.1.0.dev0.dist-info/scm_version.json +8 -0
  46. precisionai_agrieval-0.1.0.dev0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,2 @@
1
+ # Copyright 2026 Precision AI
2
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,2 @@
1
+ # Copyright 2026 Precision AI
2
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,2 @@
1
+ # Copyright 2026 Precision AI
2
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,103 @@
1
+ # Copyright 2026 Precision AI
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Unified FastAPI application factory and CLI entry point for all AgriEval wirings."""
5
+
6
+ from __future__ import annotations
7
+
8
+ import argparse
9
+ import os
10
+
11
+ import uvicorn
12
+ from fastapi import FastAPI
13
+
14
+ from precisionai.agrieval.emb.api.routes.evaluate import router as emb_router
15
+ from precisionai.agrieval.seg.api.routes.evaluate import router as seg_router
16
+
17
+ _DESCRIPTION = """
18
+ Precision AI AgriEval — evaluation framework for agricultural computer vision models.
19
+
20
+ ## Embedding evaluation
21
+
22
+ Benchmark how well image embeddings cluster by L1/L2 crop-class hierarchy.
23
+
24
+ ```
25
+ POST /v1/embeddings/evaluate/image2image
26
+ POST /v1/embeddings/evaluate/plant2image
27
+ POST /v1/embeddings/evaluate/plant2plant
28
+ ```
29
+
30
+ ## Segmentation evaluation
31
+
32
+ Evaluate predicted colour-coded masks against ground-truth masks. Returns
33
+ per-class IoU, Dice/F1, accuracy, mIoU, mAcc, and FWIoU.
34
+
35
+ ```
36
+ POST /v1/segmentation/evaluate
37
+ ```
38
+
39
+ ## Path resolution
40
+
41
+ All path arguments (``pred_dir``, ``masks_dir``, ``classes_path``, etc.) are
42
+ resolved against the ``dataset_root`` field in the request body, which falls
43
+ back to the ``PAI_DATASET_ROOT`` environment variable and then ``"dataset"``.
44
+ Absolute paths bypass ``dataset_root`` entirely.
45
+ """
46
+
47
+ app = FastAPI(
48
+ title="precisionai-agrieval",
49
+ description=_DESCRIPTION,
50
+ version="0.1.0",
51
+ )
52
+
53
+ app.include_router(emb_router, prefix="/v1")
54
+ app.include_router(seg_router, prefix="/v1")
55
+
56
+
57
+ def main() -> None:
58
+ """Parse CLI arguments and start the uvicorn ASGI server.
59
+
60
+ Recognised arguments
61
+ --------------------
62
+ --dataset-root : str
63
+ Default dataset root used when a request does not supply one. Also
64
+ settable via the ``PAI_DATASET_ROOT`` environment variable. Defaults
65
+ to ``"dataset"``.
66
+ --host : str
67
+ Bind address for the server. Defaults to ``"0.0.0.0"``.
68
+ --port : int
69
+ TCP port to listen on. Defaults to ``8000``.
70
+ --no-reload : flag
71
+ Disable uvicorn auto-reload (recommended in production).
72
+ """
73
+ parser = argparse.ArgumentParser(description="precisionai-agrieval unified API server")
74
+ parser.add_argument(
75
+ "--dataset-root",
76
+ default="dataset",
77
+ metavar="PATH",
78
+ help="Default dataset root used when the request omits dataset_root. "
79
+ "Can also be set via PAI_DATASET_ROOT env var. (default: dataset)",
80
+ )
81
+ parser.add_argument("--host", default="0.0.0.0", metavar="HOST")
82
+ parser.add_argument("--port", type=int, default=8000, metavar="PORT")
83
+ parser.add_argument(
84
+ "--no-reload",
85
+ dest="reload",
86
+ action="store_false",
87
+ default=True,
88
+ help="Disable auto-reload (recommended in production)",
89
+ )
90
+ args = parser.parse_args()
91
+
92
+ os.environ.setdefault("PAI_DATASET_ROOT", args.dataset_root)
93
+
94
+ uvicorn.run(
95
+ "precisionai.agrieval.api.app:app",
96
+ host=args.host,
97
+ port=args.port,
98
+ reload=args.reload,
99
+ )
100
+
101
+
102
+ if __name__ == "__main__":
103
+ main()
@@ -0,0 +1,31 @@
1
+ # Copyright 2026 Precision AI
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Shared runtime configuration resolved from environment variables.
5
+
6
+ ``PAI_DATASET_ROOT`` — default dataset root used when a request does not
7
+ supply ``dataset_root``. Set by the ``--dataset-root`` CLI argument before
8
+ uvicorn spawns worker processes so the value is inherited.
9
+
10
+ Defaults to ``"dataset"`` so a repository checked out next to a ``dataset/``
11
+ directory works out of the box.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import os
17
+
18
+
19
+ def get_dataset_root() -> str:
20
+ """Return the server-level default dataset root.
21
+
22
+ The value is read from the ``PAI_DATASET_ROOT`` environment variable at
23
+ request time so that it reflects any changes made after server start.
24
+
25
+ Returns
26
+ -------
27
+ str
28
+ Dataset root path, defaulting to ``"dataset"`` when the environment
29
+ variable is not set.
30
+ """
31
+ return os.environ.get("PAI_DATASET_ROOT", "dataset")
@@ -0,0 +1,28 @@
1
+ # Copyright 2026 Precision AI
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ from precisionai.agrieval.emb.schemas.evaluate import MetadataGroup
5
+ from precisionai.agrieval.emb.services.evaluate import (
6
+ run_image2image_eval,
7
+ run_plant2image_eval,
8
+ run_plant2plant_eval,
9
+ )
10
+ from precisionai.agrieval.emb.services.reporting import (
11
+ plot_cosine_similarity,
12
+ plot_knn_confusion,
13
+ plot_lle,
14
+ plot_tsne,
15
+ print_result,
16
+ )
17
+
18
+ __all__ = [
19
+ "MetadataGroup",
20
+ "plot_cosine_similarity",
21
+ "plot_knn_confusion",
22
+ "plot_lle",
23
+ "plot_tsne",
24
+ "print_result",
25
+ "run_image2image_eval",
26
+ "run_plant2image_eval",
27
+ "run_plant2plant_eval",
28
+ ]
@@ -0,0 +1,2 @@
1
+ # Copyright 2026 Precision AI
2
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,114 @@
1
+ # Copyright 2026 Precision AI
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """FastAPI application factory and entry point."""
5
+
6
+ from __future__ import annotations
7
+
8
+ import argparse
9
+ import os
10
+
11
+ import uvicorn
12
+ from fastapi import FastAPI
13
+
14
+ from precisionai.agrieval.emb.api.routes.evaluate import router as evaluate_router
15
+
16
+ _DESCRIPTION = """
17
+ Evaluate the quality of image model embeddings — how well they cluster by L1/L2 cluster.
18
+
19
+ ## Usage
20
+
21
+ Send a single JSON object with your embeddings dict to **POST /v1/embeddings/evaluate/image2image**.
22
+ Everything else is optional.
23
+
24
+ ```json
25
+ {
26
+ "embeddings": {
27
+ "images/A1/pai-abc123.png": [0.12, -0.31, 0.27, ...],
28
+ "images/A2/pai-def456.png": [0.11, -0.30, 0.26, ...],
29
+ "images/D1/pai-ghi789.png": [-0.45, 0.18, -0.09, ...]
30
+ }
31
+ }
32
+ ```
33
+
34
+ **Embedding requirements**
35
+ - Flat 1-D array (length = `embedding_dim`)
36
+ - L2-normalised float32 (`‖v‖₂ = 1.0 ± 0.001`)
37
+ - All vectors must share the same length
38
+ - Minimum 2 embeddings per request
39
+
40
+ **Path convention**
41
+
42
+ The L1 cluster label is extracted automatically from the L2 folder name:
43
+ ```
44
+ images / A1 / pai-abc123.png
45
+ ^^
46
+ L2 folder → L1 = "A" (leading letters)
47
+ ```
48
+
49
+ Supply a `metadata` dict to enable graded nDCG and per-attribute KPIs based on
50
+ the explicit L1/L2 cluster structure (see `image2image.json` in the dataset root).
51
+
52
+ **Response**
53
+
54
+ Returns a fixed JSON report with a `global_metrics` summary and a
55
+ `per_class` breakdown for every detected L1 cluster.
56
+ """
57
+
58
+ app = FastAPI(
59
+ title="precisionai-agrieval",
60
+ description=_DESCRIPTION,
61
+ version="0.1.0",
62
+ )
63
+
64
+ app.include_router(evaluate_router, prefix="/v1")
65
+
66
+
67
+ def main() -> None:
68
+ """Parse CLI arguments and start the uvicorn ASGI server.
69
+
70
+ Recognised arguments
71
+ --------------------
72
+ --dataset-root : str
73
+ Default dataset root passed to the evaluation service when a request
74
+ does not supply one. Also settable via the ``PAI_DATASET_ROOT``
75
+ environment variable. Defaults to ``"dataset"``.
76
+ --host : str
77
+ Bind address for the server. Defaults to ``"0.0.0.0"``.
78
+ --port : int
79
+ TCP port to listen on. Defaults to ``8000``.
80
+ --no-reload : flag
81
+ Disable uvicorn auto-reload (recommended in production).
82
+ """
83
+ parser = argparse.ArgumentParser(description="precisionai-agrieval API server")
84
+ parser.add_argument(
85
+ "--dataset-root",
86
+ default="dataset",
87
+ metavar="PATH",
88
+ help="Default dataset root used when the request omits dataset_root. "
89
+ "Can also be set via PAI_DATASET_ROOT env var. (default: dataset)",
90
+ )
91
+ parser.add_argument("--host", default="0.0.0.0", metavar="HOST")
92
+ parser.add_argument("--port", type=int, default=8000, metavar="PORT")
93
+ parser.add_argument(
94
+ "--no-reload",
95
+ dest="reload",
96
+ action="store_false",
97
+ default=True,
98
+ help="Disable auto-reload (recommended in production)",
99
+ )
100
+ args = parser.parse_args()
101
+
102
+ # Propagate to child worker processes via env var before uvicorn forks.
103
+ os.environ.setdefault("PAI_DATASET_ROOT", args.dataset_root)
104
+
105
+ uvicorn.run(
106
+ "precisionai.agrieval.emb.api.app:app",
107
+ host=args.host,
108
+ port=args.port,
109
+ reload=args.reload,
110
+ )
111
+
112
+
113
+ if __name__ == "__main__":
114
+ main()
@@ -0,0 +1,8 @@
1
+ # Copyright 2026 Precision AI
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Re-export of the shared dataset-root configuration for the emb sub-package."""
5
+
6
+ from precisionai.agrieval.api.config import get_dataset_root
7
+
8
+ __all__ = ["get_dataset_root"]
@@ -0,0 +1,2 @@
1
+ # Copyright 2026 Precision AI
2
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,126 @@
1
+ # Copyright 2026 Precision AI
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Embedding evaluation routes.
5
+
6
+ Three named endpoints cover the full agricultural retrieval taxonomy:
7
+
8
+ - ``POST /evaluate/image2image`` — Image→Image (full field image vs. full field image)
9
+ - ``POST /evaluate/plant2image`` — Plant→Image (instance crop vs. full field image)
10
+ - ``POST /evaluate/plant2plant`` — Plant→Plant (instance crop vs. instance crop)
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from collections.abc import Callable
16
+ from typing import Any
17
+
18
+ from fastapi import APIRouter, HTTPException
19
+
20
+ from precisionai.agrieval.emb.api.config import get_dataset_root
21
+ from precisionai.agrieval.emb.schemas.evaluate import (
22
+ EmbeddingEvaluateRequest,
23
+ EmbeddingEvaluateResponse,
24
+ Plant2ImageRequest,
25
+ Plant2PlantRequest,
26
+ )
27
+ from precisionai.agrieval.emb.services.evaluate import (
28
+ run_image2image_eval,
29
+ run_plant2image_eval,
30
+ run_plant2plant_eval,
31
+ )
32
+
33
+ router = APIRouter(prefix="/embeddings", tags=["evaluate"])
34
+
35
+
36
+ def _resolve_dataset_root(dataset_root: str | None) -> str:
37
+ """Use the configured server default when a request omits dataset_root."""
38
+ return dataset_root if dataset_root is not None else get_dataset_root()
39
+
40
+
41
+ def _run_or_400(func: Callable[..., dict[str, Any]], /, **kwargs: Any) -> EmbeddingEvaluateResponse:
42
+ """Convert service-layer ValueError exceptions into 400 responses."""
43
+ try:
44
+ result = func(**kwargs)
45
+ except ValueError as exc:
46
+ raise HTTPException(status_code=400, detail=str(exc)) from exc
47
+ return EmbeddingEvaluateResponse(**result)
48
+
49
+
50
+ @router.post("/evaluate/image2image", response_model=EmbeddingEvaluateResponse)
51
+ def evaluate_image2image(request: EmbeddingEvaluateRequest) -> EmbeddingEvaluateResponse:
52
+ """Evaluate embedding quality for the Image→Image retrieval scenario.
53
+
54
+ Given a complete agricultural field image, retrieve visually and
55
+ agronomically similar full field images. Similarity can be coarse
56
+ (crop type, soil, growth stage) or fine (disease symptoms, weed pressure).
57
+
58
+ Pass a dict of ``image_path → embedding`` vectors. When ``metadata`` is
59
+ omitted the L1 class label is inferred from the folder name of each path
60
+ (e.g. ``images/A1/img.png`` → ``A``). Supply ``metadata`` to enable
61
+ explicit-positive groups, graded nDCG (0-3 by L2/L1 hierarchy and
62
+ ``plants`` overlap), and per-attribute KPIs.
63
+
64
+ **Only ``embeddings`` is required** — all other fields use server defaults.
65
+
66
+ Returns a fixed JSON report with:
67
+ - **global_metrics** — pairwise similarity stats, intra/inter class gap,
68
+ KNN label purity, effective rank, centroid similarity, duplicate counts.
69
+ - **per_class** — the same metrics broken down per crop class.
70
+ """
71
+ return _run_or_400(
72
+ run_image2image_eval,
73
+ image_embeddings=request.embeddings,
74
+ k_values=request.k_values,
75
+ dataset_root=_resolve_dataset_root(request.dataset_root),
76
+ sample_pairs=request.sample_pairs,
77
+ metadata=request.metadata,
78
+ )
79
+
80
+
81
+ @router.post("/evaluate/plant2image", response_model=EmbeddingEvaluateResponse)
82
+ def evaluate_plant2image(request: Plant2ImageRequest) -> EmbeddingEvaluateResponse:
83
+ """Evaluate embedding quality for the Plant→Image retrieval scenario.
84
+
85
+ Pass a mixed corpus of full-field image embeddings and per-plant instance
86
+ crop embeddings together with an ``instance_to_image`` mapping that
87
+ declares which instances were cropped from which parent image.
88
+
89
+ The mapping is the explicit-positive ground truth: when an instance is the
90
+ query its parent full image is the grade-3 positive; when a full image is
91
+ the query all its instances are grade-3 positives. Items that share the
92
+ same class label (inferred from the parent's folder name) are grade-2
93
+ positives across groups.
94
+
95
+ Returns the same fixed JSON report as ``POST /v1/embeddings/evaluate``
96
+ with metadata-aware KPIs enabled (``knn_metadata_precision``,
97
+ ``knn_metadata_ndcg``, ``alignment``, etc.).
98
+ """
99
+ return _run_or_400(
100
+ run_plant2image_eval,
101
+ embeddings=request.embeddings,
102
+ instance_to_image=request.instance_to_image,
103
+ k_values=request.k_values,
104
+ dataset_root=_resolve_dataset_root(request.dataset_root),
105
+ sample_pairs=request.sample_pairs,
106
+ )
107
+
108
+
109
+ @router.post("/evaluate/plant2plant", response_model=EmbeddingEvaluateResponse)
110
+ def evaluate_plant2plant(request: Plant2PlantRequest) -> EmbeddingEvaluateResponse:
111
+ """Evaluate embedding quality for the Plant→Plant retrieval scenario.
112
+
113
+ Pass per-plant instance crop embeddings with an ``instance_labels`` mapping
114
+ that assigns each instance its crop or weed class. All instances sharing
115
+ the same class label are treated as mutual explicit positives.
116
+
117
+ Returns the same fixed JSON report as ``POST /v1/embeddings/evaluate``
118
+ with metadata-aware KPIs enabled.
119
+ """
120
+ return _run_or_400(
121
+ run_plant2plant_eval,
122
+ embeddings=request.embeddings,
123
+ instance_labels=request.instance_labels,
124
+ k_values=request.k_values,
125
+ sample_pairs=request.sample_pairs,
126
+ )
@@ -0,0 +1,115 @@
1
+ # Copyright 2026 Precision AI
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ from precisionai.agrieval.emb.metrics.analysis import analyze_embedding_space, compare_embedding_spaces
5
+ from precisionai.agrieval.emb.metrics.cross_model import (
6
+ knn_jaccard_at_k,
7
+ knn_overlap_at_k,
8
+ pairwise_similarity_correlation,
9
+ per_item_neighbor_disagreement,
10
+ )
11
+ from precisionai.agrieval.emb.metrics.duplicates import (
12
+ duplicate_groups_at_threshold,
13
+ duplicate_pairs_at_threshold,
14
+ )
15
+ from precisionai.agrieval.emb.metrics.geometry import (
16
+ alignment,
17
+ anisotropy_summary,
18
+ centroid_similarity_stats,
19
+ effective_rank,
20
+ pca_explained_variance,
21
+ uniformity,
22
+ )
23
+ from precisionai.agrieval.emb.metrics.label_aware import (
24
+ ImageItem,
25
+ intra_inter_similarity_gap,
26
+ knn_confusion_matrix,
27
+ knn_label_mrr_at_k,
28
+ knn_label_ndcg_at_k,
29
+ knn_label_purity_at_k,
30
+ knn_label_r_precision,
31
+ knn_map_at_k,
32
+ knn_metadata_map_at_k,
33
+ knn_metadata_mrr_at_k,
34
+ knn_metadata_ndcg_at_k,
35
+ knn_metadata_precision_at_k,
36
+ knn_metadata_r_precision,
37
+ knn_per_attribute_ndcg_at_k,
38
+ relevance_grade,
39
+ )
40
+ from precisionai.agrieval.emb.metrics.neighbors import (
41
+ gini_coefficient,
42
+ hubness_at_k,
43
+ knn_radius_at_k,
44
+ mean_top_k_similarity,
45
+ outlier_score_at_k,
46
+ )
47
+ from precisionai.agrieval.emb.metrics.ranking import (
48
+ average_precision_at_k,
49
+ dcg_at_k,
50
+ l2_normalize,
51
+ ndcg_at_k,
52
+ normalize_ks,
53
+ precision_at_k,
54
+ r_precision,
55
+ recall_at_k,
56
+ reciprocal_rank,
57
+ safe_mean,
58
+ )
59
+ from precisionai.agrieval.emb.metrics.similarity import (
60
+ cosine_similarity_matrix,
61
+ pairwise_similarity_stats,
62
+ similarity_threshold_counts,
63
+ top_k_neighbors,
64
+ )
65
+
66
+ __all__ = [
67
+ "ImageItem",
68
+ "alignment",
69
+ "analyze_embedding_space",
70
+ "anisotropy_summary",
71
+ "average_precision_at_k",
72
+ "centroid_similarity_stats",
73
+ "compare_embedding_spaces",
74
+ "cosine_similarity_matrix",
75
+ "dcg_at_k",
76
+ "duplicate_groups_at_threshold",
77
+ "duplicate_pairs_at_threshold",
78
+ "effective_rank",
79
+ "gini_coefficient",
80
+ "hubness_at_k",
81
+ "intra_inter_similarity_gap",
82
+ "knn_confusion_matrix",
83
+ "knn_jaccard_at_k",
84
+ "knn_label_mrr_at_k",
85
+ "knn_label_ndcg_at_k",
86
+ "knn_label_purity_at_k",
87
+ "knn_label_r_precision",
88
+ "knn_map_at_k",
89
+ "knn_metadata_map_at_k",
90
+ "knn_metadata_mrr_at_k",
91
+ "knn_metadata_ndcg_at_k",
92
+ "knn_metadata_precision_at_k",
93
+ "knn_metadata_r_precision",
94
+ "knn_overlap_at_k",
95
+ "knn_per_attribute_ndcg_at_k",
96
+ "knn_radius_at_k",
97
+ "l2_normalize",
98
+ "mean_top_k_similarity",
99
+ "ndcg_at_k",
100
+ "normalize_ks",
101
+ "outlier_score_at_k",
102
+ "pairwise_similarity_correlation",
103
+ "pairwise_similarity_stats",
104
+ "pca_explained_variance",
105
+ "per_item_neighbor_disagreement",
106
+ "precision_at_k",
107
+ "r_precision",
108
+ "recall_at_k",
109
+ "reciprocal_rank",
110
+ "relevance_grade",
111
+ "safe_mean",
112
+ "similarity_threshold_counts",
113
+ "top_k_neighbors",
114
+ "uniformity",
115
+ ]