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.
- precisionai/__init__.py +2 -0
- precisionai/agrieval/__init__.py +2 -0
- precisionai/agrieval/api/__init__.py +2 -0
- precisionai/agrieval/api/app.py +103 -0
- precisionai/agrieval/api/config.py +31 -0
- precisionai/agrieval/emb/__init__.py +28 -0
- precisionai/agrieval/emb/api/__init__.py +2 -0
- precisionai/agrieval/emb/api/app.py +114 -0
- precisionai/agrieval/emb/api/config.py +8 -0
- precisionai/agrieval/emb/api/routes/__init__.py +2 -0
- precisionai/agrieval/emb/api/routes/evaluate.py +126 -0
- precisionai/agrieval/emb/metrics/__init__.py +115 -0
- precisionai/agrieval/emb/metrics/_utils.py +240 -0
- precisionai/agrieval/emb/metrics/analysis.py +202 -0
- precisionai/agrieval/emb/metrics/cross_model.py +235 -0
- precisionai/agrieval/emb/metrics/duplicates.py +190 -0
- precisionai/agrieval/emb/metrics/geometry.py +282 -0
- precisionai/agrieval/emb/metrics/label_aware.py +805 -0
- precisionai/agrieval/emb/metrics/neighbors.py +195 -0
- precisionai/agrieval/emb/metrics/ranking.py +258 -0
- precisionai/agrieval/emb/metrics/similarity.py +212 -0
- precisionai/agrieval/emb/schemas/__init__.py +14 -0
- precisionai/agrieval/emb/schemas/evaluate.py +471 -0
- precisionai/agrieval/emb/services/__init__.py +2 -0
- precisionai/agrieval/emb/services/evaluate.py +1117 -0
- precisionai/agrieval/emb/services/reporting.py +754 -0
- precisionai/agrieval/seg/__init__.py +9 -0
- precisionai/agrieval/seg/api/__init__.py +2 -0
- precisionai/agrieval/seg/api/config.py +8 -0
- precisionai/agrieval/seg/api/routes/__init__.py +2 -0
- precisionai/agrieval/seg/api/routes/evaluate.py +75 -0
- precisionai/agrieval/seg/cli.py +73 -0
- precisionai/agrieval/seg/metrics/__init__.py +22 -0
- precisionai/agrieval/seg/metrics/segmentation.py +165 -0
- precisionai/agrieval/seg/schemas/__init__.py +2 -0
- precisionai/agrieval/seg/schemas/evaluate.py +129 -0
- precisionai/agrieval/seg/services/__init__.py +2 -0
- precisionai/agrieval/seg/services/evaluate.py +573 -0
- precisionai_agrieval-0.1.0.dev0.dist-info/METADATA +441 -0
- precisionai_agrieval-0.1.0.dev0.dist-info/RECORD +46 -0
- precisionai_agrieval-0.1.0.dev0.dist-info/WHEEL +5 -0
- precisionai_agrieval-0.1.0.dev0.dist-info/entry_points.txt +4 -0
- precisionai_agrieval-0.1.0.dev0.dist-info/licenses/LICENSE.md +201 -0
- precisionai_agrieval-0.1.0.dev0.dist-info/scm_file_list.json +201 -0
- precisionai_agrieval-0.1.0.dev0.dist-info/scm_version.json +8 -0
- precisionai_agrieval-0.1.0.dev0.dist-info/top_level.txt +1 -0
precisionai/__init__.py
ADDED
|
@@ -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,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,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
|
+
]
|