diff --git a/README.md b/README.md index 2d86a93..51c732d 100644 --- a/README.md +++ b/README.md @@ -78,10 +78,13 @@ heatmap/segmentation artifacts, training curves, and weight manifest status. The same panel can run `POST /api/acceptance/smoke`, a lightweight live smoke that creates an upload dataset, uploads a label, downloads it through the artifact API, runs a mock job, checks SSE log streaming, and executes one -legacy image/label overlay job on tiny generated PNGs. It also runs model -family readiness checks: a SegModel/SMP forward pass, a YOLO segmentation -prediction on a tiny image, MMSeg config parsing, and local MMSeg pretrained -weight discovery. MMSeg full-model readiness is validated in +legacy image/label overlay job on tiny generated PNGs. It also launches +`yolo.predict_custom` and `yolo.heatmap_custom` through the normal job queue +against the uploaded sample image, proving that upload datasets can produce +browsable segmentation and heatmap artifacts. It also runs model family +readiness checks: a SegModel/SMP forward pass, a YOLO segmentation prediction +on a tiny image, MMSeg config parsing, and local MMSeg pretrained weight +discovery. MMSeg full-model readiness is validated in `SEG_MMSEG_CONDA_ENV` by importing `mmcv._ext` and building a local MMSeg `EncoderDecoder` from the existing config tree. diff --git a/backend/app/acceptance.py b/backend/app/acceptance.py index 2cdebd5..273a2c4 100644 --- a/backend/app/acceptance.py +++ b/backend/app/acceptance.py @@ -267,6 +267,19 @@ def _write_acceptance_images(root: Path) -> tuple[Path, Path, Path]: return image_path, label_path, result_dir +def _relative_to_project(path: Path) -> str: + try: + return str(path.resolve().relative_to(settings.project_root)) + except ValueError: + return str(path.resolve()) + + +def _result_files(root: Path, suffixes: set[str]) -> list[Path]: + if not root.exists(): + return [] + return sorted(path for path in root.rglob("*") if path.is_file() and path.suffix.lower() in suffixes) + + def run_model_family_readiness() -> dict[str, Any]: """Exercise the model-family runtime stack without launching full training.""" source = settings.source_root @@ -501,6 +514,61 @@ def run_live_acceptance(base_url: str = "http://127.0.0.1:8010") -> dict[str, An } ) + yolo_weight = settings.source_root / "Seg_All_In_One_YoloModel" / "yolo11n-seg.pt" + uploaded_image_source = settings.project_root / "var" / "uploads" / "datasets" / dataset_name / "images" + predict_name = f"{dataset_name}_predict_smoke" + predict = _create_job_and_wait( + base_url, + "yolo.predict_custom", + { + "weights": str(yolo_weight), + "source": _relative_to_project(uploaded_image_source), + "project": "var/custom_yolo_runs", + "name": predict_name, + "imgsz": 64, + "conf": 0.05, + "device": "cpu", + "exist_ok": True, + }, + timeout=120, + ) + predict_root = settings.project_root / "var" / "custom_yolo_runs" / predict_name + predict_outputs = _result_files(predict_root, {".png", ".jpg", ".jpeg"}) + checks.append( + { + "name": "uploaded_yolo_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_smoke" + heatmap = _create_job_and_wait( + base_url, + "yolo.heatmap_custom", + { + "weights": str(yolo_weight), + "source": _relative_to_project(uploaded_image_source), + "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, + ) + 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": "uploaded_yolo_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]]}, + } + ) + mock = _create_job_and_wait(base_url, "mock.echo", {"message": f"acceptance {run_id}"}, timeout=45) mock_log = mock.get("polled", {}).get("job", {}).get("log_tail", "") checks.append({"name": "mock_job_runner", "passed": mock.get("passed", False) and f"acceptance {run_id}" in mock_log, "detail": mock}) diff --git a/backend/app/agents/evaluation_agent.py b/backend/app/agents/evaluation_agent.py index 97b7dcc..6776dac 100644 --- a/backend/app/agents/evaluation_agent.py +++ b/backend/app/agents/evaluation_agent.py @@ -59,6 +59,7 @@ def evaluate_project() -> dict: "deep_acceptance_api": "/api/acceptance/deep" in backend_text, "deep_acceptance_ui": "runDeepAcceptance" in frontend_text and "深度训练" in frontend_text, "deep_yolo_heatmap_validation": "yolo_tiny_heatmap_generation" in acceptance_text, + "uploaded_yolo_workflow_acceptance": "uploaded_yolo_predict_job_runner" in acceptance_text and "uploaded_yolo_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"], @@ -83,7 +84,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, and synthetic deep training/heatmap acceptance; next focus is running a real user-supplied dataset through the full workflow.") + suggestions.append("Current platform covers the requested control-plane features, uploaded YOLO dataset train/predict/heatmap actions, live uploaded-data YOLO predict/heatmap acceptance, and synthetic deep training acceptance; next focus is a real non-synthetic dataset run.") score = sum(1 for item in checks if item["passed"]) / max(len(checks), 1) return {