Add real YOLO train acceptance

This commit is contained in:
2026-06-30 23:33:43 +08:00
parent 5055084788
commit fb96c96d8b
10 changed files with 270 additions and 16 deletions

View File

@@ -447,6 +447,13 @@ def latest_real_acceptance_report() -> dict[str, Any]:
return json.loads(path.read_text(encoding="utf-8"))
def latest_real_train_acceptance_report() -> dict[str, Any]:
path = settings.project_root / "var" / "acceptance" / "real_train_latest.json"
if not path.exists():
return {"available": False, "path": str(path)}
return json.loads(path.read_text(encoding="utf-8"))
def run_real_dataset_acceptance(base_url: str = "http://127.0.0.1:8010") -> dict[str, Any]:
"""Run the upload/predict/heatmap path against existing non-synthetic Seg data."""
acceptance_root = settings.project_root / "var" / "acceptance"
@@ -620,6 +627,178 @@ def run_real_dataset_acceptance(base_url: str = "http://127.0.0.1:8010") -> dict
return report
def run_real_train_acceptance(base_url: str = "http://127.0.0.1:8010") -> dict[str, Any]:
"""Run a short YOLO train/predict/heatmap loop using real workspace samples."""
acceptance_root = settings.project_root / "var" / "acceptance"
run_id = uuid.uuid4().hex[:8]
fixture_root = acceptance_root / f"real_train_{run_id}"
fixture_root.mkdir(parents=True, exist_ok=True)
samples = find_real_workspace_samples()
checks: list[dict[str, Any]] = [
{"name": "real_train_workspace_samples_discovered", "passed": samples["passed"], "detail": samples}
]
if not samples["passed"]:
report = {
"available": True,
"run_id": run_id,
"base_url": base_url,
"fixture_root": str(fixture_root),
"passed": False,
"checks": checks,
"created_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
}
(acceptance_root / "real_train_latest.json").write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8")
return report
dataset_name = f"real_train_acceptance_{run_id}"
created_dataset = _request_json("POST", f"{base_url}/api/datasets", {"name": dataset_name, "description": "real workspace short train acceptance"}, timeout=10)
checks.append({"name": "create_real_train_upload_dataset", "passed": created_dataset.get("passed", False), "detail": created_dataset})
yolo_image = Path(samples["yolo_pair"]["image"])
yolo_label = Path(samples["yolo_pair"]["label"])
uploads = {
"real_train_yolo_image_upload": _post_file(f"{base_url}/api/datasets/{dataset_name}/upload/images", yolo_image, timeout=30),
"real_train_yolo_label_upload": _post_file(f"{base_url}/api/datasets/{dataset_name}/upload/labels", yolo_label, timeout=30),
}
for name, detail in uploads.items():
checks.append({"name": name, "passed": detail.get("passed", False), "detail": detail})
validation = _request_json("GET", f"{base_url}/api/datasets/{dataset_name}/validate", timeout=20)
validation_json = validation.get("json") if validation.get("passed") else {}
checks.append(
{
"name": "real_train_dataset_validate_yolo",
"passed": validation.get("passed", False) and validation_json.get("ready", {}).get("yolo"),
"detail": validation,
}
)
class_count = max(validation_json.get("classes") or [0]) + 1
class_names = ["object"] + [f"class_{index}" for index in range(1, class_count)]
yolo_yaml = _request_json("POST", f"{base_url}/api/datasets/{dataset_name}/yolo-yaml", {"class_names": class_names}, timeout=20)
yolo_yaml_json = yolo_yaml.get("json") if yolo_yaml.get("passed") else {}
checks.append({"name": "real_train_dataset_yolo_yaml", "passed": yolo_yaml.get("passed", False), "detail": yolo_yaml})
train_name = f"{dataset_name}_train"
train = _create_job_and_wait(
base_url,
"yolo.train_custom",
{
"data": yolo_yaml_json.get("relative_path", f"var/uploads/datasets/{dataset_name}/dataset.yaml"),
"model": str(settings.source_root / "Seg_All_In_One_YoloModel" / "yolo11n-seg.pt"),
"project": "var/custom_yolo_runs",
"name": train_name,
"epochs": 1,
"imgsz": 96,
"batch": 1,
"workers": 0,
"device": "cpu",
"exist_ok": True,
},
timeout=240,
)
train_root = settings.project_root / "var" / "custom_yolo_runs" / train_name
best_weight = train_root / "weights" / "best.pt"
last_weight = train_root / "weights" / "last.pt"
results_csv = train_root / "results.csv"
checks.append(
{
"name": "real_train_yolo_one_epoch_job_runner",
"passed": train.get("passed", False) and best_weight.exists() and results_csv.exists() and results_csv.stat().st_size > 0,
"detail": {
**train,
"best_weight": _relative_to_project(best_weight) if best_weight.exists() else None,
"last_weight": _relative_to_project(last_weight) if last_weight.exists() else None,
"results_csv": _relative_to_project(results_csv) if results_csv.exists() else None,
"results_csv_size": results_csv.stat().st_size if results_csv.exists() else 0,
},
}
)
uploaded_image_json = uploads["real_train_yolo_image_upload"].get("json", {})
uploaded_image = uploaded_image_json.get("saved", [{}])[0].get("relative_path")
predict_name = f"{dataset_name}_predict_trained"
if best_weight.exists() and uploaded_image:
predict = _create_job_and_wait(
base_url,
"yolo.predict_custom",
{
"weights": str(best_weight),
"source": uploaded_image,
"project": "var/custom_yolo_runs",
"name": predict_name,
"imgsz": 96,
"conf": 0.01,
"device": "cpu",
"exist_ok": True,
},
timeout=120,
)
else:
predict = {"passed": False, "error": "skipped because training did not produce best.pt or upload path"}
predict_root = settings.project_root / "var" / "custom_yolo_runs" / predict_name
predict_outputs = _result_files(predict_root, {".png", ".jpg", ".jpeg"})
checks.append(
{
"name": "real_train_trained_weight_predict_job_runner",
"passed": predict.get("passed", False) and bool(predict_outputs),
"detail": {**predict, "output_count": len(predict_outputs), "outputs": [_relative_to_project(path) for path in predict_outputs[:8]]},
}
)
heatmap_name = f"{dataset_name}_heatmap_trained"
if best_weight.exists() and uploaded_image:
heatmap = _create_job_and_wait(
base_url,
"yolo.heatmap_custom",
{
"weights": str(best_weight),
"source": uploaded_image,
"project": "var/custom_yolo_runs",
"name": heatmap_name,
"model_key": "YOLO11n-seg",
"pt_name": "best.pt",
"cam_method": "GradCAM",
"target_layers": "model.model.model[9]",
"limit": 1,
},
timeout=120,
)
else:
heatmap = {"passed": False, "error": "skipped because training did not produce best.pt or upload path"}
heatmap_root = settings.project_root / "var" / "custom_yolo_runs" / heatmap_name / "HeartMap_Visual"
heatmap_outputs = _result_files(heatmap_root, {".jpg", ".jpeg", ".png"})
checks.append(
{
"name": "real_train_trained_weight_heatmap_job_runner",
"passed": heatmap.get("passed", False) and len(heatmap_outputs) >= 2,
"detail": {**heatmap, "output_count": len(heatmap_outputs), "outputs": [_relative_to_project(path) for path in heatmap_outputs[:8]]},
}
)
report = {
"available": True,
"run_id": run_id,
"base_url": base_url,
"fixture_root": str(fixture_root),
"dataset_name": dataset_name,
"samples": samples,
"artifacts": {
"train_root": _relative_to_project(train_root),
"best_weight": _relative_to_project(best_weight) if best_weight.exists() else None,
"results_csv": _relative_to_project(results_csv) if results_csv.exists() else None,
"predict_outputs": [_relative_to_project(path) for path in predict_outputs[:8]],
"heatmap_outputs": [_relative_to_project(path) for path in heatmap_outputs[:8]],
},
"passed": all(item["passed"] for item in checks),
"checks": checks,
"created_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
}
(acceptance_root / "real_train_latest.json").write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8")
return report
def run_deep_acceptance() -> dict[str, Any]:
"""Run minimal training loops for each model family without full datasets."""
acceptance_root = settings.project_root / "var" / "acceptance"

View File

@@ -108,6 +108,12 @@ def evaluate_project() -> dict:
and "real_workspace_yolo_predict_job_runner" in acceptance_text
and "real_workspace_yolo_heatmap_job_runner" in acceptance_text
and "real_workspace_stack_job_runner" in acceptance_text,
"real_train_acceptance": "/api/acceptance/real-train" in backend_text
and "runRealTrainAcceptance" in frontend_text
and "真实短训" in frontend_text
and "real_train_yolo_one_epoch_job_runner" in acceptance_text
and "real_train_trained_weight_predict_job_runner" in acceptance_text
and "real_train_trained_weight_heatmap_job_runner" in acceptance_text,
"agent_api": "/api/agents/evaluate" in backend_text and "/api/agents/validate" in backend_text,
"agent_panel_ui": "runAgentValidation" in frontend_text and "评价建议" in frontend_text and "Validation Agent" in frontend_text,
"coverage_api": "/api/coverage" in backend_text and coverage["task_build_passed"],
@@ -132,7 +138,7 @@ def evaluate_project() -> dict:
if coverage["unmapped_user_scripts"]:
suggestions.append(f"Map remaining user-facing scripts: {len(coverage['unmapped_user_scripts'])}")
if not suggestions:
suggestions.append("Current platform covers the requested control-plane features, uploaded YOLO dataset train/predict/heatmap actions, live uploaded-data YOLO predict/heatmap acceptance, real workspace data acceptance, and synthetic deep training acceptance; next focus is a longer operator-run task on a full dataset.")
suggestions.append("Current platform covers the requested control-plane features, uploaded YOLO dataset train/predict/heatmap actions, live uploaded-data YOLO predict/heatmap acceptance, real workspace data acceptance, real short-train acceptance, and synthetic deep training acceptance; next focus is a longer operator-run task on a full dataset.")
passed_count = sum(1 for item in checks if item["passed"])
total_count = max(len(checks), 1)

View File

@@ -8,7 +8,7 @@ import urllib.error
import urllib.request
from pathlib import Path
from ..acceptance import run_deep_acceptance, run_live_acceptance, run_real_dataset_acceptance
from ..acceptance import run_deep_acceptance, run_live_acceptance, run_real_dataset_acceptance, run_real_train_acceptance
from ..capabilities import get_capability_matrix
from ..catalog import get_catalog
from ..config import settings
@@ -47,6 +47,7 @@ def validate_project(
run_acceptance: bool | None = None,
run_deep: bool | None = None,
run_real: bool | None = None,
run_real_train: bool | None = None,
run_live: bool | None = None,
) -> dict:
"""Validate current runtime readiness without launching heavy training."""
@@ -122,8 +123,9 @@ def validate_project(
acceptance_enabled = run_acceptance if run_acceptance is not None else os.getenv("SEG_VALIDATE_ACCEPTANCE", "0") == "1"
deep_enabled = run_deep if run_deep is not None else os.getenv("SEG_VALIDATE_DEEP", "1") == "1"
real_enabled = run_real if run_real is not None else os.getenv("SEG_VALIDATE_REAL", "0") == "1"
real_train_enabled = run_real_train if run_real_train is not None else os.getenv("SEG_VALIDATE_REAL_TRAIN", "0") == "1"
live_enabled = run_live if run_live is not None else os.getenv("SEG_VALIDATE_LIVE", "0") == "1"
live_enabled = live_enabled or acceptance_enabled or real_enabled
live_enabled = live_enabled or acceptance_enabled or real_enabled or real_train_enabled
if live_enabled:
backend_url = os.getenv("SEG_VALIDATE_BACKEND_URL", "http://127.0.0.1:8010")
@@ -166,6 +168,9 @@ def validate_project(
if real_enabled:
real_acceptance = run_real_dataset_acceptance(backend_url)
checks.append({"name": "real_workspace_acceptance", "passed": real_acceptance["passed"], "detail": real_acceptance})
if real_train_enabled:
real_train_acceptance = run_real_train_acceptance(backend_url)
checks.append({"name": "real_train_acceptance", "passed": real_train_acceptance["passed"], "detail": real_train_acceptance})
if deep_enabled:
deep_acceptance = run_deep_acceptance()
checks.append({"name": "deep_training_acceptance", "passed": deep_acceptance["passed"], "detail": deep_acceptance})

View File

@@ -3,7 +3,12 @@ from __future__ import annotations
import time
from typing import Any
from .acceptance import latest_acceptance_report, latest_deep_acceptance_report, latest_real_acceptance_report
from .acceptance import (
latest_acceptance_report,
latest_deep_acceptance_report,
latest_real_acceptance_report,
latest_real_train_acceptance_report,
)
from .catalog import get_catalog
from .coverage import get_coverage_report
from .modules.dataset.service import list_uploaded_datasets
@@ -157,6 +162,7 @@ def get_capability_matrix() -> dict[str, Any]:
gpus = get_gpus()
acceptance = latest_acceptance_report()
real_acceptance = latest_real_acceptance_report()
real_train_acceptance = latest_real_train_acceptance_report()
deep_acceptance = latest_deep_acceptance_report()
all_tasks = catalog["task_types"]
@@ -277,6 +283,12 @@ def get_capability_matrix() -> dict[str, Any]:
"passed": bool(real_acceptance.get("passed")),
"detail": real_acceptance.get("run_id", "not run"),
},
{
"id": "real_train_acceptance",
"label": "真实短训练验收",
"passed": bool(real_train_acceptance.get("passed")),
"detail": real_train_acceptance.get("run_id", "not run"),
},
{
"id": "weights_manifest",
"label": "权重清单",
@@ -302,6 +314,7 @@ def get_capability_matrix() -> dict[str, Any]:
"gpus_available": bool(gpus.get("available")),
"acceptance_passed": bool(acceptance.get("passed")),
"real_acceptance_passed": bool(real_acceptance.get("passed")),
"real_train_acceptance_passed": bool(real_train_acceptance.get("passed")),
"deep_acceptance_passed": bool(deep_acceptance.get("passed")),
},
"requirements": requirements,

View File

@@ -13,9 +13,11 @@ from .acceptance import (
latest_acceptance_report,
latest_deep_acceptance_report,
latest_real_acceptance_report,
latest_real_train_acceptance_report,
run_deep_acceptance,
run_live_acceptance,
run_real_dataset_acceptance,
run_real_train_acceptance,
)
from .capabilities import get_capability_matrix
from .catalog import get_catalog
@@ -131,6 +133,16 @@ def api_real_acceptance(base_url: str = "http://127.0.0.1:8010") -> dict:
return run_real_dataset_acceptance(base_url)
@app.get("/api/acceptance/real-train/latest")
def api_real_train_acceptance_latest() -> dict:
return latest_real_train_acceptance_report()
@app.post("/api/acceptance/real-train")
def api_real_train_acceptance(base_url: str = "http://127.0.0.1:8010") -> dict:
return run_real_train_acceptance(base_url)
@app.get("/api/datasets")
def api_datasets() -> list[dict]:
return list_uploaded_datasets()
@@ -295,6 +307,7 @@ def api_agent_validate(
run_acceptance: bool = False,
run_deep: bool = False,
run_real: bool = False,
run_real_train: bool = False,
run_live: bool | None = None,
) -> dict:
return validate_project(
@@ -302,5 +315,6 @@ def api_agent_validate(
run_acceptance=run_acceptance,
run_deep=run_deep,
run_real=run_real,
run_real_train=run_real_train,
run_live=run_live,
)