diff --git a/README.md b/README.md index 3aebbce..3ef6fa5 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,11 @@ resize, pair-check, label rebuild, transparent overlay, stitch, and video-frame jobs. Segmentation previews, YOLO heatmaps, and loss/metric artifacts are grouped on the results dashboard. +The coverage panel calls `GET /api/coverage` and verifies that the user-facing +scripts from the existing `Seg/` workspace are mapped to web jobs. MMSeg +vendored internals, docs, build outputs, converters, and config templates are +classified as supporting artifacts rather than direct web actions. + ## Weight Sync The current workspace contains tens of GB of pretrained and trained weights. @@ -73,17 +78,24 @@ machine after cloning the code. The backend exposes all current Seg capabilities as job types. Examples: - `dataset.rename`, `dataset.resize`, `dataset.pair`, `dataset.rebuild_labels`, - `dataset.stack`, `dataset.stitch`, `dataset.video_frames` + `dataset.stack`, `dataset.stitch`, `dataset.video_frames`, + `dataset.yolo_check_pairs`, `dataset.yolo_stack`, `dataset.yolo_txt_sort`, + `dataset.yolo_convert_png`, `dataset.yolo_resize` - `segmodel.train`, `segmodel.batch_train`, `segmodel.predict`, - `segmodel.batch_predict`, `segmodel.flops`, `segmodel.raw_mask_check` + `segmodel.batch_predict`, `segmodel.flops`, `segmodel.params_flops`, + `segmodel.benchmark`, `segmodel.raw_mask_check` - `yolo.train`, `yolo.batch_train`, `yolo.predict`, `yolo.batch_predict`, `yolo.heatmap`, `yolo.compare`, `yolo.raw_mask_check`, `yolo.video_visible` - `mmseg.generate_data`, `mmseg.generate_alg`, `mmseg.train`, `mmseg.metrics`, `mmseg.flops_fps`, `mmseg.draw`, `mmseg.extract_loss_miou` +- `visual.train`, `visual.inference`, `visual.fps`, + `visual.yolo11_heatmap_v1`, `visual.yolo11_heatmap_v2`, `visual.deal_labels` - `analysis.all`, `system.backup`, `mock.echo` Use `GET /api/catalog` to inspect supported models, algorithms, datasets, and task types discovered from the existing `Seg/` workspace. +Use `GET /api/coverage` to inspect script-to-task coverage and task +buildability. ## Agents diff --git a/backend/app/agents/evaluation_agent.py b/backend/app/agents/evaluation_agent.py index 03b6a1e..1310196 100644 --- a/backend/app/agents/evaluation_agent.py +++ b/backend/app/agents/evaluation_agent.py @@ -4,16 +4,21 @@ from pathlib import Path from ..catalog import get_catalog from ..config import settings +from ..coverage import get_coverage_report REQUIRED_TASKS = { "dataset.upload": "covered_by_api", "dataset.video_frames": "job", + "dataset.yolo_txt_sort": "job", "segmodel.train": "job", "segmodel.predict": "job", "yolo.heatmap": "job", + "yolo.video_visible": "job", "mmseg.flops_fps": "job", "analysis.all": "job", + "visual.fps": "job", + "system.check_graph_card": "job", } @@ -23,6 +28,7 @@ def evaluate_project() -> dict: backend = settings.project_root / "backend" / "app" / "main.py" readme = settings.project_root / "README.md" catalog = get_catalog() + coverage = get_coverage_report() checks = [] suggestions = [] @@ -35,8 +41,12 @@ def evaluate_project() -> dict: "upload_ui": "uploadDatasetFiles" in frontend_text and "labels" in frontend_text and "masks" in frontend_text, "loss_result_ui": "loss" in frontend_text.lower() and "heatmap" in frontend_text.lower(), "dataset_api": "/api/datasets" in backend_text and "api_upload_dataset_files" in backend_text, + "coverage_api": "/api/coverage" in backend_text and coverage["task_build_passed"], + "visual_tools": "visual.yolo11_heatmap_v2" in catalog["task_types"] and "visual.fps" in catalog["task_types"], + "yolo_dataset_tools": "dataset.yolo_txt_sort" in catalog["task_types"] and "dataset.yolo_resize" in catalog["task_types"], "no_weight_to_gitea": "Do not push" in readme_text and "check_no_weight_git" in readme_text, "all_core_tasks": all(task in catalog["task_types"] for task in REQUIRED_TASKS if REQUIRED_TASKS[task] == "job"), + "mapped_user_scripts": not coverage["unmapped_user_scripts"], } for name, passed in expectations.items(): @@ -48,6 +58,8 @@ def evaluate_project() -> dict: suggestions.append("MMSeg algorithm catalog should expose all 31 algorithm generators.") if len(catalog["segmodel_architectures"]) < 12: suggestions.append("SegModel catalog should expose all 12 supported architectures.") + 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; next focus is real dataset/training acceptance tests.") @@ -58,4 +70,3 @@ def evaluate_project() -> dict: "checks": checks, "suggestions": suggestions, } - diff --git a/backend/app/agents/validation_agent.py b/backend/app/agents/validation_agent.py index 9b1b5e9..2984160 100644 --- a/backend/app/agents/validation_agent.py +++ b/backend/app/agents/validation_agent.py @@ -10,6 +10,7 @@ from pathlib import Path from ..catalog import get_catalog from ..config import settings +from ..coverage import get_coverage_report from ..modules.system.service import get_conda_envs, get_gpus from ..modules.weights.service import load_manifest @@ -28,10 +29,10 @@ def _run(command: list[str], cwd: Path | None = None, timeout: int = 60) -> dict def _fetch(url: str, timeout: int = 5) -> dict: try: with urllib.request.urlopen(url, timeout=timeout) as response: - body = response.read(20000).decode("utf-8", errors="replace") + body = response.read(200000).decode("utf-8", errors="replace") return {"url": url, "status": response.status, "body": body, "passed": 200 <= response.status < 300} except urllib.error.HTTPError as exc: - body = exc.read(20000).decode("utf-8", errors="replace") + body = exc.read(200000).decode("utf-8", errors="replace") return {"url": url, "status": exc.code, "body": body, "passed": False} except Exception as exc: return {"url": url, "error": str(exc), "passed": False} @@ -42,9 +43,14 @@ def validate_project(run_build: bool = False) -> dict: checks = [] catalog = get_catalog() manifest = load_manifest() + coverage = get_coverage_report() checks.append({"name": "catalog_has_yolo_heatmap", "passed": "yolo.heatmap" in catalog["task_types"]}) checks.append({"name": "catalog_has_mmseg_31_algs", "passed": len(catalog["mmseg_algorithms"]) >= 31}) + checks.append({"name": "catalog_has_visual_tools", "passed": "visual.yolo11_heatmap_v2" in catalog["task_types"] and "visual.fps" in catalog["task_types"]}) + checks.append({"name": "catalog_has_yolo_dataset_tools", "passed": "dataset.yolo_txt_sort" in catalog["task_types"] and "dataset.yolo_convert_png" in catalog["task_types"]}) + checks.append({"name": "task_buildability", "passed": coverage["task_build_passed"], "detail": coverage["task_build_checks"]}) + checks.append({"name": "script_coverage_user_facing", "passed": not coverage["unmapped_user_scripts"], "detail": coverage}) checks.append({"name": "weights_manifest_present", "passed": manifest.get("count", 0) >= 1}) checks.append({"name": "gpus_query", "passed": bool(get_gpus().get("available"))}) env_names = [item["name"] for item in get_conda_envs().get("envs", [])] @@ -72,9 +78,11 @@ def validate_project(run_build: bool = False) -> dict: frontend_url = os.getenv("SEG_VALIDATE_FRONTEND_URL", "http://127.0.0.1:5173") health = _fetch(f"{backend_url}/api/health") datasets = _fetch(f"{backend_url}/api/datasets") + live_coverage = _fetch(f"{backend_url}/api/coverage") frontend = _fetch(frontend_url) checks.append({"name": "live_backend_health", "passed": health["passed"] and '"ok":true' in health.get("body", "").replace(" ", ""), "detail": health}) checks.append({"name": "live_dataset_api", "passed": datasets["passed"] and datasets.get("body", "").lstrip().startswith("["), "detail": datasets}) + checks.append({"name": "live_coverage_api", "passed": live_coverage["passed"] and '"task_build_passed":true' in live_coverage.get("body", "").replace(" ", ""), "detail": live_coverage}) checks.append({"name": "live_frontend_index", "passed": frontend["passed"] and "Seg Data Server" in frontend.get("body", ""), "detail": frontend}) if run_build: diff --git a/backend/app/catalog.py b/backend/app/catalog.py index 803f156..99be1de 100644 --- a/backend/app/catalog.py +++ b/backend/app/catalog.py @@ -44,41 +44,185 @@ YOLO_MODELS = [ TASK_TYPES = [ "mock.echo", "system.backup", + "system.check_graph_card", "dataset.rename", "dataset.to_png", "dataset.resize", + "dataset.resize_single", "dataset.pair", "dataset.rebuild_labels", + "dataset.deal_labels", + "dataset.deal_labels_old", "dataset.stack", + "dataset.stack_single", "dataset.stitch", + "dataset.stitch_single", + "dataset.run_wizard", + "dataset.stack_pair_check", + "dataset.stack_tool_batch", + "dataset.stack_tool_single", "dataset.video_frames", + "dataset.yolo_check_pairs", + "dataset.yolo_stack", + "dataset.yolo_stack_single", + "dataset.yolo_rebuild_labels", + "dataset.yolo_txt_ori", + "dataset.yolo_txt_sort", + "dataset.yolo_convert_png", + "dataset.yolo_resize", "segmodel.train", "segmodel.batch_train", "segmodel.predict", "segmodel.batch_predict", "segmodel.flops", + "segmodel.params_flops", + "segmodel.benchmark", "segmodel.raw_mask_check", "segmodel.metrics", + "segmodel.copy_best", "yolo.train", "yolo.batch_train", "yolo.predict", + "yolo.predict_v1", "yolo.batch_predict", "yolo.heatmap", "yolo.compare", "yolo.raw_mask_check", + "yolo.copy_best", "yolo.video_visible", "yolo.video_unvisible", + "yolo.layer_tester", "mmseg.init_weights", "mmseg.generate_data", + "mmseg.generate_data_v1", + "mmseg.generate_data_legacy", "mmseg.generate_alg", + "mmseg.generate_alg_v1", + "mmseg.generate_alg_legacy", "mmseg.train", "mmseg.metrics", + "mmseg.metrics_v1", "mmseg.flops_fps", + "mmseg.flops_fps_v1", "mmseg.draw", "mmseg.extract_loss_miou", + "mmseg.delete_epoch", + "mmseg.copy_result", + "mmseg.predict_v1", + "mmseg.predict_v2", + "visual.train", + "visual.inference", + "visual.fps", + "visual.yolo11_heatmap_v1", + "visual.yolo11_heatmap_v2", + "visual.label_ori", + "visual.label_sort", + "visual.gen_8bit_png", + "visual.deal_labels", + "visual.tool_deal_labels_demo", "analysis.all", ] +COMMON_LABEL_DEFAULTS = { + "src_label_fold": "./Label", + "save_pro_label_fold": "./ORI_pro_label_fold", + "save_GT_label_fold": "./ORI_GT_label_fold", + "Label_Max_Search_layer": 1000, + "pro_append_name": "_label", + "GT_append_name": "", + "GT_channel": 1, + "back_gnd_color": 0, + "first_class_color": 1, + "pic_type": "png", + "Max_width": 10000, + "Rebuild_from": "label", + "Rebuild_to": "GT", + "save_process_pics": False, +} + + +TASK_DEFAULTS: dict[str, dict[str, Any]] = { + "mock.echo": {"message": "hello from Seg Data Server"}, + "system.backup": {}, + "system.check_graph_card": {}, + "dataset.rename": {"image_dir": "../DataSet_Own/ORI", "label_dir": "../DataSet_Own/Label"}, + "dataset.to_png": {"input_dir": "../DataSet_Own/ORI", "output_dir": "../DataSet_Own/ORI_PNG"}, + "dataset.resize": {"image_dir": "../DataSet_Own/ORI", "label_dir": "../DataSet_Own/Label", "width": 1920, "height": 1080}, + "dataset.resize_single": {"folder": "../DataSet_Own/ORI", "nearest": False, "width": 1920, "height": 1080}, + "dataset.pair": {"image_dir": "../DataSet_Own/ORI", "label_dir": "../DataSet_Own/Label", "prefix": "", "suffix": ""}, + "dataset.rebuild_labels": {"label_dir": "../DataSet_Own/Label"}, + "dataset.deal_labels": COMMON_LABEL_DEFAULTS, + "dataset.deal_labels_old": COMMON_LABEL_DEFAULTS, + "dataset.stack": {"image_dir": "../DataSet_Own/ORI", "label_dir": "../DataSet_Own/Label", "result_dir": "../DataSet_Own/stacked", "alpha": 0.3}, + "dataset.stack_single": {"image_path": "../DataSet_Own/ORI/example.png", "label_path": "../DataSet_Own/Label/example.png", "result_dir": "../DataSet_Own/stacked", "alpha": 0.3}, + "dataset.stitch": {"image_dir": "../DataSet_Own/ORI", "label_dir": "../DataSet_Own/Label", "result_dir": "../DataSet_Own/stitch"}, + "dataset.stitch_single": {"image_path": "../DataSet_Own/ORI/example.png", "label_path": "../DataSet_Own/Label/example.png", "result_dir": "../DataSet_Own/stitch", "relative_pos": "up_down"}, + "dataset.run_wizard": {"image_dir": "../DataSet_Own/ORI", "label_dir": "../DataSet_Own/Label", "stdin_text": "7\n"}, + "dataset.stack_pair_check": {"image_dir": "../DataSet_Own/ORI", "label_dir": "../DataSet_Own/Label"}, + "dataset.stack_tool_batch": {"image_dir": "../DataSet_Own/ORI", "label_dir": "../DataSet_Own/Label", "result_dir": "../DataSet_Own/stacked", "alpha": 0.3}, + "dataset.stack_tool_single": {"image_path": "../DataSet_Own/ORI/example.png", "label_path": "../DataSet_Own/Label/example.png", "result_dir": "../DataSet_Own/stacked", "alpha": 0.3}, + "dataset.video_frames": {"video": "../Seg_Predict_Own_Video_V2/LC_Video_1.mp4", "interval": 0.5, "resize": "1920x1080"}, + "dataset.yolo_check_pairs": {"image_dir": "../Seg_All_In_One_YoloModel/Yolo数据集构建/ORI", "label_dir": "../Seg_All_In_One_YoloModel/Yolo数据集构建/Label", "yes": False}, + "dataset.yolo_stack": {"image_dir": "../Seg_All_In_One_YoloModel/Yolo数据集构建/ORI", "label_dir": "../Seg_All_In_One_YoloModel/Yolo数据集构建/Label", "result_dir": "../Seg_All_In_One_YoloModel/Yolo数据集构建/stacked", "alpha": 0.3}, + "dataset.yolo_stack_single": {"image_path": "../Seg_All_In_One_YoloModel/Yolo数据集构建/ORI/example.png", "label_path": "../Seg_All_In_One_YoloModel/Yolo数据集构建/Label/example.png", "result_dir": "../Seg_All_In_One_YoloModel/Yolo数据集构建/stacked", "alpha": 0.3}, + "dataset.yolo_rebuild_labels": COMMON_LABEL_DEFAULTS, + "dataset.yolo_txt_ori": {}, + "dataset.yolo_txt_sort": {}, + "dataset.yolo_convert_png": {"folder": "../Seg_All_In_One_YoloModel/Yolo数据集构建/Data/images/train", "delete_source": False, "workers": 4}, + "dataset.yolo_resize": {"folder": "../Seg_All_In_One_YoloModel/Yolo数据集构建/Data/images/train", "size": 1080, "workers": 4}, + "segmodel.train": {"architecture": "Unet"}, + "segmodel.batch_train": {}, + "segmodel.predict": {"architecture": "Unet", "run_choice": 1}, + "segmodel.batch_predict": {}, + "segmodel.flops": {"script": "2_predict_params_and_FLOPs_V2.py"}, + "segmodel.params_flops": {"architecture": "Unet", "shape": [512, 512]}, + "segmodel.benchmark": {"architecture": "Unet", "shape": [512, 512], "repeat_times": 3}, + "segmodel.raw_mask_check": {}, + "segmodel.metrics": {}, + "segmodel.copy_best": {}, + "yolo.train": {"model": "YOLOv8n-seg"}, + "yolo.batch_train": {}, + "yolo.predict": {"model": "YOLOv8n-seg", "pt_name": "best.pt", "conf": 0.2, "run_choice": 1}, + "yolo.predict_v1": {"model": "YOLOv8n-seg", "pt_name": "best.pt", "conf": 0.2, "run_choice": 1}, + "yolo.batch_predict": {"pt_name": "best.pt", "conf": 0.2}, + "yolo.heatmap": {"model": "YOLOv8n-seg", "cam_method": "All", "pt_name": "best.pt", "run_choice": 1}, + "yolo.compare": {"pt_name": "all"}, + "yolo.raw_mask_check": {"pt_name": "best.pt"}, + "yolo.copy_best": {"pt_name": "best.pt"}, + "yolo.video_visible": {}, + "yolo.video_unvisible": {}, + "yolo.layer_tester": {}, + "mmseg.init_weights": {}, + "mmseg.generate_data": {}, + "mmseg.generate_data_v1": {}, + "mmseg.generate_data_legacy": {}, + "mmseg.generate_alg": {"dataset_choice": 1, "gpu_count": 1, "gpu_ids": [0], "schedule_mode": 2, "max_epochs": 300, "algorithm_choice": 1}, + "mmseg.generate_alg_v1": {"dataset_choice": 1, "gpu_count": 1, "gpu_ids": [0], "schedule_mode": 2, "max_epochs": 300, "algorithm_choice": 1}, + "mmseg.generate_alg_legacy": {}, + "mmseg.train": {"config": "configs/example.py", "work_dir": "../DataSet_Public_outputs/example"}, + "mmseg.metrics": {"input_dir": "../Hardisk", "output_dir": "../BestMode_Predict_Results_DataSet_Public", "dataset_choice": 1, "algorithm_choice": 0}, + "mmseg.metrics_v1": {"dataset_choice": 1, "algorithm_choice": 0}, + "mmseg.flops_fps": {"input_dir": "../Hardisk", "output_dir": "../BestMode_Predict_Results_DataSet_Public", "repeat_times": 3, "dataset_choice": 1, "algorithm_choice": 0}, + "mmseg.flops_fps_v1": {"dataset_choice": 1, "algorithm_choice": 0}, + "mmseg.draw": {}, + "mmseg.extract_loss_miou": {}, + "mmseg.delete_epoch": {}, + "mmseg.copy_result": {}, + "mmseg.predict_v1": {}, + "mmseg.predict_v2": {}, + "visual.train": {}, + "visual.inference": {}, + "visual.fps": {"weights": "yolov8n.pt", "batch": 1, "imgs": [640, 640], "device": "cpu", "warmup": 10, "testtime": 20, "half": False}, + "visual.yolo11_heatmap_v1": {}, + "visual.yolo11_heatmap_v2": {}, + "visual.label_ori": {}, + "visual.label_sort": {}, + "visual.gen_8bit_png": {}, + "visual.deal_labels": COMMON_LABEL_DEFAULTS, + "visual.tool_deal_labels_demo": {}, + "analysis.all": {"input_dir": "../BestMode_Predict_Results_DataSet_Public", "output_dir": "./", "dataset_choice": 1}, +} + def _read_json(path: Path) -> Any | None: try: @@ -136,6 +280,7 @@ def get_catalog() -> dict[str, Any]: "source_root": str(settings.source_root), "project_root": str(settings.project_root), "task_types": TASK_TYPES, + "task_defaults": TASK_DEFAULTS, "segmodel_architectures": SEGMODEL_ARCHS, "yolo_models": YOLO_MODELS, "mmseg_algorithms": discover_mmseg_algorithms(), diff --git a/backend/app/coverage.py b/backend/app/coverage.py new file mode 100644 index 0000000..b20a4ac --- /dev/null +++ b/backend/app/coverage.py @@ -0,0 +1,191 @@ +from __future__ import annotations + +from pathlib import Path +from typing import Any + +from .catalog import TASK_DEFAULTS, TASK_TYPES +from .config import settings +from .modules import build_module_task +from .paths import rel + + +SCRIPT_TASK_MAP: dict[str, str] = { + "Back_Up.sh": "system.backup", + "Check_Graph_Card.sh": "system.check_graph_card", + "DataSet_Own/1. 图片预处理(内含使用手册)/1_rename_pics.sh": "dataset.rename", + "DataSet_Own/1. 图片预处理(内含使用手册)/2_1_Trans_to_png.py": "dataset.to_png", + "DataSet_Own/1. 图片预处理(内含使用手册)/2_2_Resize.py": "dataset.resize_single", + "DataSet_Own/1. 图片预处理(内含使用手册)/2_reformate_pics.sh": "dataset.resize", + "DataSet_Own/1. 图片预处理(内含使用手册)/3_pair_ori_label.sh": "dataset.pair", + "DataSet_Own/1. 图片预处理(内含使用手册)/4_deal_labels.py": "dataset.deal_labels", + "DataSet_Own/1. 图片预处理(内含使用手册)/4_deal_labels_old(老版程序).py": "dataset.deal_labels_old", + "DataSet_Own/1. 图片预处理(内含使用手册)/4_rebuild_labels.sh": "dataset.rebuild_labels", + "DataSet_Own/1. 图片预处理(内含使用手册)/5_TOOL_stack_pics.sh": "dataset.stack", + "DataSet_Own/1. 图片预处理(内含使用手册)/5_stack_picture.py": "dataset.stack_single", + "DataSet_Own/1. 图片预处理(内含使用手册)/6_TOOL_stitch_pics.sh": "dataset.stitch", + "DataSet_Own/1. 图片预处理(内含使用手册)/6_stitch_picture.py": "dataset.stitch_single", + "DataSet_Own/1. 图片预处理(内含使用手册)/Seg_data_run.sh": "dataset.run_wizard", + "Seg_All_In_One_Analysis/1_Analysis_All.py": "analysis.all", + "Seg_All_In_One_MMSeg/My_All_In_One/0_Initial_Save_All_Model_locally.py": "mmseg.init_weights", + "Seg_All_In_One_MMSeg/My_All_In_One/1_Initial_Data_All-ori.py": "mmseg.generate_data_legacy", + "Seg_All_In_One_MMSeg/My_All_In_One/1_Initial_Data_All_data_from_1_Data_Parameter-V1.py": "mmseg.generate_data_v1", + "Seg_All_In_One_MMSeg/My_All_In_One/1_Initial_Data_All_data_from_1_Data_Parameter-V2.py": "mmseg.generate_data", + "Seg_All_In_One_MMSeg/My_All_In_One/2_Initial_Alg_All-ori-old.py": "mmseg.generate_alg_legacy", + "Seg_All_In_One_MMSeg/My_All_In_One/2_Initial_Alg_All_data_from_2_Alg_Program-V1.py": "mmseg.generate_alg_v1", + "Seg_All_In_One_MMSeg/My_All_In_One/2_Initial_Alg_All_data_from_2_Alg_Program-V2.py": "mmseg.generate_alg", + "Seg_All_In_One_MMSeg/My_All_In_One/3_Find_And_Delete_Special_Epoch.py": "mmseg.delete_epoch", + "Seg_All_In_One_MMSeg/My_All_In_One/3_Tool_Copy_Result_To_Hardisk.sh": "mmseg.copy_result", + "Seg_All_In_One_MMSeg/My_All_In_One/4_1_predict_params_FLOPs_FPS_V2.py": "mmseg.flops_fps", + "Seg_All_In_One_MMSeg/My_All_In_One/4_1_predict_params_and_FLOPs_V1.py": "mmseg.flops_fps_v1", + "Seg_All_In_One_MMSeg/My_All_In_One/4_2_predict_matrics_from_log_V1.py": "mmseg.metrics_v1", + "Seg_All_In_One_MMSeg/My_All_In_One/4_2_predict_matrics_from_log_V2.py": "mmseg.metrics", + "Seg_All_In_One_MMSeg/My_All_In_One/4_3_predict_draw_pictures_and_tabels.py": "mmseg.draw", + "Seg_All_In_One_MMSeg/My_All_In_One/4_4_extract_loss_and_best_miou.py": "mmseg.extract_loss_miou", + "Seg_All_In_One_MMSeg/My_All_In_One/x4_Predict_V1-.py": "mmseg.predict_v1", + "Seg_All_In_One_MMSeg/My_All_In_One/x4_Predict_V2-.py": "mmseg.predict_v2", + "Seg_All_In_One_MMSeg/tools/train.py": "mmseg.train", + "Seg_All_In_One_SegModel/1_predict.py": "segmodel.predict", + "Seg_All_In_One_SegModel/1_predict_raw_masks_check.py": "segmodel.raw_mask_check", + "Seg_All_In_One_SegModel/2_predict_params_and_FLOPs_V1.py": "segmodel.flops", + "Seg_All_In_One_SegModel/2_predict_params_and_FLOPs_V2.py": "segmodel.flops", + "Seg_All_In_One_SegModel/3_predict_matrics_from_log.py": "segmodel.metrics", + "Seg_All_In_One_SegModel/Tool_Copy_Best_Model.sh": "segmodel.copy_best", + "Seg_All_In_One_SegModel/Tool_benchmark_smp.py": "segmodel.benchmark", + "Seg_All_In_One_SegModel/Tool_get_params_and_FLOPs.py": "segmodel.params_flops", + "Seg_All_In_One_SegModel/predict.sh": "segmodel.batch_predict", + "Seg_All_In_One_SegModel/train.py": "segmodel.train", + "Seg_All_In_One_SegModel/train.sh": "segmodel.batch_train", + "Seg_All_In_One_YoloModel/Tool_Yolo_Copy_Best_Model.sh": "yolo.copy_best", + "Seg_All_In_One_YoloModel/Yolo可视化测试/yolo_layer_tester.py": "yolo.layer_tester", + "Seg_All_In_One_YoloModel/Yolo数据集构建/0_1_check_picture_pair.py": "dataset.yolo_check_pairs", + "Seg_All_In_One_YoloModel/Yolo数据集构建/0_2_TOOL_stack_pics.sh": "dataset.yolo_stack", + "Seg_All_In_One_YoloModel/Yolo数据集构建/0_2_stack_picture.py": "dataset.yolo_stack_single", + "Seg_All_In_One_YoloModel/Yolo数据集构建/1_deal_labels.py": "dataset.yolo_rebuild_labels", + "Seg_All_In_One_YoloModel/Yolo数据集构建/2_Check_and_Gen_Txt_Label_ori_label.py": "dataset.yolo_txt_ori", + "Seg_All_In_One_YoloModel/Yolo数据集构建/2_Check_and_Gen_Txt_Label_sort_label.py": "dataset.yolo_txt_sort", + "Seg_All_In_One_YoloModel/Yolo数据集构建/Tool_convert_bmp_jpg_to_png.py": "dataset.yolo_convert_png", + "Seg_All_In_One_YoloModel/Yolo数据集构建/Tool_resize_pics.py": "dataset.yolo_resize", + "Seg_All_In_One_YoloModel/yolo_predict.sh": "yolo.batch_predict", + "Seg_All_In_One_YoloModel/yolo_predict_V1.py": "yolo.predict_v1", + "Seg_All_In_One_YoloModel/yolo_predict_V2.py": "yolo.predict", + "Seg_All_In_One_YoloModel/yolo_predict_V2_compare_all.py": "yolo.compare", + "Seg_All_In_One_YoloModel/yolo_predict_raw_masks_check.py": "yolo.raw_mask_check", + "Seg_All_In_One_YoloModel/yolo_predict_visualize_nn.py": "yolo.heatmap", + "Seg_All_In_One_YoloModel/yolo_train.py": "yolo.train", + "Seg_All_In_One_YoloModel/yolo_train.sh": "yolo.batch_train", + "Seg_Predict_Own_Video_V2/1_Save_Frame_V1.py": "dataset.video_frames", + "Seg_Predict_Own_Video_V2/1_Save_Frame_V2.py": "dataset.video_frames", + "Seg_Predict_YoloModel/yolo_Seg_Video-V1-Visible.py": "yolo.video_visible", + "Seg_Predict_YoloModel/yolo_Seg_Video-V2-UnVisible.py": "yolo.video_unvisible", + "Tool-可视化/0_图片Labels生成/4_deal_labels.py": "visual.deal_labels", + "Tool-可视化/0_图片Labels生成/Tool_deal_labels.py": "visual.tool_deal_labels_demo", + "Tool-可视化/Tool_Check_and_Gen_Txt_Label_ori_label.py": "visual.label_ori", + "Tool-可视化/Tool_Check_and_Gen_Txt_Label_sort_label.py": "visual.label_sort", + "Tool-可视化/Tool_Gen_8_Bit_PNG[没用,不认].py": "visual.gen_8bit_png", + "Tool-可视化/get_FPS.py": "visual.fps", + "Tool-可视化/inference.py": "visual.inference", + "Tool-可视化/train.py": "visual.train", + "Tool-可视化/yolov11_heatmap_V1.py": "visual.yolo11_heatmap_v1", + "Tool-可视化/yolov11_heatmap_V2.py": "visual.yolo11_heatmap_v2", + "Tool-图片堆叠/1_check_picture_pair.py": "dataset.stack_pair_check", + "Tool-图片堆叠/2_TOOL_stack_pics.sh": "dataset.stack_tool_batch", + "Tool-图片堆叠/2_stack_picture.py": "dataset.stack_tool_single", +} + +SUPPORTING_SCRIPT_PATTERNS = ( + "Seg_All_In_One_MMSeg/demo/", + "Seg_All_In_One_MMSeg/build/", + "Seg_All_In_One_MMSeg/configs/", + "Seg_All_In_One_MMSeg/docker/", + "Seg_All_In_One_MMSeg/docs/", + "Seg_All_In_One_MMSeg/projects/", + "Seg_All_In_One_MMSeg/mmseg/", + "Seg_All_In_One_MMSeg/tests/", + "Seg_All_In_One_MMSeg/setup.py", + "Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/", + "Seg_All_In_One_MMSeg/My_All_In_One/Initial_Alg_Program/", + "Seg_All_In_One_MMSeg/My_All_In_One/Initial_Data_Program/", + "Seg_All_In_One_MMSeg/My_All_In_One/Initial_Schedule_Program/", + "Seg_All_In_One_MMSeg/tools/dist_", + "Seg_All_In_One_MMSeg/tools/slurm_", + "Seg_All_In_One_MMSeg/tools/test.py", + "Seg_All_In_One_MMSeg/tools/analysis_tools/", + "Seg_All_In_One_MMSeg/tools/dataset_converters/", + "Seg_All_In_One_MMSeg/tools/deployment/", + "Seg_All_In_One_MMSeg/tools/misc/", + "Seg_All_In_One_MMSeg/tools/model_converters/", + "Seg_All_In_One_MMSeg/tools/torchserve/", + "Seg_All_In_One_SegModel/config.py", + "Seg_All_In_One_SegModel/dataset.py", + "Seg_All_In_One_SegModel/loss.py", + "Seg_All_In_One_SegModel/utils.py", + "Seg_All_In_One_YoloModel/yolo_config.py", + "Seg_All_In_One_YoloModel/Yolo数据集构建/Tool_Classes_And_Palette.py", + "Seg_All_In_One_YoloModel/Yolo数据集构建/Tool_deal_labels.py", + "Seg_Predict_YoloModel/yolo_config.py", + "Seg_Predict_YoloModel/yolo_train.py", + "DataSet_Own/2. 图片分割程序(mmseg)/", +) + + +def _script_inventory() -> list[str]: + scripts = [] + for path in settings.source_root.rglob("*"): + if path.is_file() and path.suffix in {".py", ".sh"}: + scripts.append(rel(path, settings.source_root)) + return sorted(scripts) + + +def _is_supporting_script(relative_path: str) -> bool: + return any(relative_path.startswith(pattern) or relative_path == pattern for pattern in SUPPORTING_SCRIPT_PATTERNS) + + +def _command_script_path(command: list[str]) -> Path | None: + if not command: + return None + if command[0] == "bash" and len(command) > 1: + return Path(command[1]) + if command[0] == "python" and len(command) > 1 and command[1] != "-c": + return Path(command[1]) + if len(command) > 5 and command[:3] == ["conda", "run", "-n"] and command[4] == "python": + return Path(command[5]) + return None + + +def build_task_checks() -> list[dict[str, Any]]: + checks = [] + for task in TASK_TYPES: + params = TASK_DEFAULTS.get(task, {}) + try: + spec = build_module_task(task, dict(params), settings.task_conda_env) + script_path = _command_script_path(spec.command) if spec else None + checks.append( + { + "task": task, + "passed": spec is not None and (script_path is None or script_path.exists()), + "command": spec.command if spec else None, + "cwd": str(spec.cwd) if spec else None, + "script_exists": None if script_path is None else script_path.exists(), + "script": None if script_path is None else str(script_path), + } + ) + except Exception as exc: + checks.append({"task": task, "passed": False, "error": str(exc)}) + return checks + + +def get_coverage_report() -> dict[str, Any]: + scripts = _script_inventory() + mapped_scripts = [script for script in scripts if script in SCRIPT_TASK_MAP] + user_scripts = [script for script in scripts if not _is_supporting_script(script)] + unmapped_user_scripts = [script for script in user_scripts if script not in SCRIPT_TASK_MAP] + task_checks = build_task_checks() + return { + "scripts_total": len(scripts), + "user_scripts_total": len(user_scripts), + "mapped_user_scripts": len(mapped_scripts), + "task_build_passed": all(item["passed"] for item in task_checks), + "unmapped_user_scripts": unmapped_user_scripts, + "script_task_map": SCRIPT_TASK_MAP, + "task_build_checks": task_checks, + } diff --git a/backend/app/main.py b/backend/app/main.py index 075b1c6..7c60b06 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -11,6 +11,7 @@ from fastapi.responses import FileResponse, StreamingResponse from . import db from .catalog import get_catalog from .config import settings +from .coverage import get_coverage_report from .jobs import cancel_job, create_job from .modules.system.service import disk_usage, get_conda_envs, get_gpus, scan_results from .modules.dataset.service import create_dataset, list_uploaded_datasets, save_upload @@ -63,6 +64,11 @@ def api_catalog() -> dict: return get_catalog() +@app.get("/api/coverage") +def api_coverage() -> dict: + return get_coverage_report() + + @app.get("/api/datasets") def api_datasets() -> list[dict]: return list_uploaded_datasets() diff --git a/backend/app/modules/__init__.py b/backend/app/modules/__init__.py index cdd7629..0a1dbfb 100644 --- a/backend/app/modules/__init__.py +++ b/backend/app/modules/__init__.py @@ -6,6 +6,7 @@ from .mmseg.tasks import build_mmseg_task from .segmodel.tasks import build_segmodel_task from .system.tasks import build_system_task from .yolo.tasks import build_yolo_task +from .visual.tasks import build_visual_task def build_module_task(job_type: str, params: dict, conda_env: str): @@ -13,6 +14,7 @@ def build_module_task(job_type: str, params: dict, conda_env: str): build_dataset_task, build_segmodel_task, build_yolo_task, + build_visual_task, build_mmseg_task, build_analysis_task, build_system_task, @@ -21,4 +23,3 @@ def build_module_task(job_type: str, params: dict, conda_env: str): if spec is not None: return spec return None - diff --git a/backend/app/modules/dataset/tasks.py b/backend/app/modules/dataset/tasks.py index 53b6811..229732b 100644 --- a/backend/app/modules/dataset/tasks.py +++ b/backend/app/modules/dataset/tasks.py @@ -16,6 +16,24 @@ def _dataset_script(name: str) -> Path: return DATASET_TOOL_DIR / name +def _append_label_rebuild_flags(args: list[str], params: dict) -> None: + append_flag(args, "-src_fold", params.get("src_label_fold")) + append_flag(args, "-save_pro_fold", params.get("save_pro_label_fold")) + append_flag(args, "-save_GT_fold", params.get("save_GT_label_fold")) + append_flag(args, "-fold_search_depth", params.get("Label_Max_Search_layer")) + append_flag(args, "-pro_suffix_name", params.get("pro_append_name")) + append_flag(args, "-GT_suffix_name", params.get("GT_append_name")) + append_flag(args, "-GT_channel", params.get("GT_channel")) + append_flag(args, "-back_gnd_color", params.get("back_gnd_color")) + append_flag(args, "-first_class_color", params.get("first_class_color")) + append_flag(args, "-pic_type", params.get("pic_type")) + append_flag(args, "-Max_width", params.get("Max_width")) + append_flag(args, "-Rebuild_from", params.get("Rebuild_from")) + append_flag(args, "-Rebuild_to", params.get("Rebuild_to")) + if "save_process_pics" in params: + append_flag(args, "-save_process_pics", str(params["save_process_pics"])) + + def build_dataset_task(job_type: str, params: dict, conda_env: str) -> CommandSpec | None: if job_type == "dataset.rename": args = bash(_dataset_script("1_rename_pics.sh")) @@ -38,6 +56,18 @@ def build_dataset_task(job_type: str, params: dict, conda_env: str) -> CommandSp append_flag(args, "-h", params.get("height", 1080)) return CommandSpec(args, DATASET_TOOL_DIR, "resize and reformat image/label folders") + if job_type == "dataset.resize_single": + script = _dataset_script("2_2_Resize.py") + args = conda_python( + conda_env, + script, + required(params, "folder"), + str(bool(params.get("nearest", False))), + params.get("width", 1920), + params.get("height", 1080), + ) + return CommandSpec(args, DATASET_TOOL_DIR, "resize one image folder with the legacy Python tool") + if job_type == "dataset.pair": args = bash(_dataset_script("3_pair_ori_label.sh")) append_flag(args, "-i", required(params, "image_dir")) @@ -51,6 +81,12 @@ def build_dataset_task(job_type: str, params: dict, conda_env: str) -> CommandSp append_flag(args, "-l", required(params, "label_dir")) return CommandSpec(args, DATASET_TOOL_DIR, "rebuild color labels into GT masks") + if job_type in {"dataset.deal_labels", "dataset.deal_labels_old"}: + script_name = "4_deal_labels_old(老版程序).py" if job_type.endswith("_old") else "4_deal_labels.py" + args = conda_python(conda_env, _dataset_script(script_name)) + _append_label_rebuild_flags(args, params) + return CommandSpec(args, DATASET_TOOL_DIR, "run legacy label rebuild/conversion script") + if job_type == "dataset.stack": args = bash(_dataset_script("5_TOOL_stack_pics.sh")) append_flag(args, "-i", required(params, "image_dir")) @@ -61,6 +97,17 @@ def build_dataset_task(job_type: str, params: dict, conda_env: str) -> CommandSp append_flag(args, "-s", params.get("suffix", "")) return CommandSpec(args, DATASET_TOOL_DIR, "overlay image and label for inspection") + if job_type == "dataset.stack_single": + args = conda_python( + conda_env, + _dataset_script("5_stack_picture.py"), + required(params, "image_path"), + required(params, "label_path"), + required(params, "result_dir"), + params.get("alpha", 0.3), + ) + return CommandSpec(args, DATASET_TOOL_DIR, "overlay one image and label pair") + if job_type == "dataset.stitch": args = bash(_dataset_script("6_TOOL_stitch_pics.sh")) append_flag(args, "-i", required(params, "image_dir")) @@ -68,6 +115,52 @@ def build_dataset_task(job_type: str, params: dict, conda_env: str) -> CommandSp append_flag(args, "-r", required(params, "result_dir")) return CommandSpec(args, DATASET_TOOL_DIR, "stitch image and label panels") + if job_type == "dataset.stitch_single": + args = conda_python( + conda_env, + _dataset_script("6_stitch_picture.py"), + required(params, "image_path"), + required(params, "label_path"), + required(params, "result_dir"), + params.get("relative_pos", "up_down"), + ) + return CommandSpec(args, DATASET_TOOL_DIR, "stitch one image and label pair") + + if job_type == "dataset.run_wizard": + args = bash(_dataset_script("Seg_data_run.sh")) + append_flag(args, "-i", required(params, "image_dir")) + append_flag(args, "-l", required(params, "label_dir")) + return CommandSpec(args, DATASET_TOOL_DIR, "run legacy interactive dataset wizard", stdin_text=params.get("stdin_text")) + + if job_type == "dataset.stack_pair_check": + args = conda_python(conda_env, STACK_TOOL_DIR / "1_check_picture_pair.py") + append_flag(args, "-i", required(params, "image_dir")) + append_flag(args, "-l", required(params, "label_dir")) + append_flag(args, "-p", params.get("prefix", "")) + append_flag(args, "-s", params.get("suffix", "")) + return CommandSpec(args, STACK_TOOL_DIR, "check image/label pair names with stack tool") + + if job_type == "dataset.stack_tool_batch": + args = bash(STACK_TOOL_DIR / "2_TOOL_stack_pics.sh") + append_flag(args, "-i", required(params, "image_dir")) + append_flag(args, "-l", required(params, "label_dir")) + append_flag(args, "-r", required(params, "result_dir")) + append_flag(args, "-a", params.get("alpha", 0.3)) + append_flag(args, "-p", params.get("prefix", "")) + append_flag(args, "-s", params.get("suffix", "")) + return CommandSpec(args, STACK_TOOL_DIR, "run standalone stack tool batch overlay") + + if job_type == "dataset.stack_tool_single": + args = conda_python( + conda_env, + STACK_TOOL_DIR / "2_stack_picture.py", + required(params, "image_path"), + required(params, "label_path"), + required(params, "result_dir"), + params.get("alpha", 0.3), + ) + return CommandSpec(args, STACK_TOOL_DIR, "run standalone stack tool for one pair") + if job_type == "dataset.video_frames": script = VIDEO_DIR / "1_Save_Frame_V2.py" args = conda_python(conda_env, script) @@ -82,7 +175,55 @@ def build_dataset_task(job_type: str, params: dict, conda_env: str) -> CommandSp args = conda_python(conda_env, script) append_flag(args, "-i", required(params, "image_dir")) append_flag(args, "-l", required(params, "label_dir")) + append_flag(args, "-p", params.get("prefix", "")) + append_flag(args, "-s", params.get("suffix", "")) + if params.get("yes"): + args.append("-y") return CommandSpec(args, YOLO_DATASET_DIR, "check YOLO image/label pairs") - return None + if job_type == "dataset.yolo_stack": + args = bash(YOLO_DATASET_DIR / "0_2_TOOL_stack_pics.sh") + append_flag(args, "-i", required(params, "image_dir")) + append_flag(args, "-l", required(params, "label_dir")) + append_flag(args, "-r", required(params, "result_dir")) + append_flag(args, "-a", params.get("alpha", 0.3)) + append_flag(args, "-p", params.get("prefix", "")) + append_flag(args, "-s", params.get("suffix", "")) + return CommandSpec(args, YOLO_DATASET_DIR, "overlay YOLO dataset image/label pairs") + if job_type == "dataset.yolo_stack_single": + args = conda_python( + conda_env, + YOLO_DATASET_DIR / "0_2_stack_picture.py", + required(params, "image_path"), + required(params, "label_path"), + required(params, "result_dir"), + params.get("alpha", 0.3), + ) + return CommandSpec(args, YOLO_DATASET_DIR, "overlay one YOLO dataset image/label pair") + + if job_type == "dataset.yolo_rebuild_labels": + args = conda_python(conda_env, YOLO_DATASET_DIR / "1_deal_labels.py") + _append_label_rebuild_flags(args, params) + return CommandSpec(args, YOLO_DATASET_DIR, "rebuild labels for YOLO dataset generation") + + if job_type == "dataset.yolo_txt_ori": + return CommandSpec(conda_python(conda_env, YOLO_DATASET_DIR / "2_Check_and_Gen_Txt_Label_ori_label.py"), YOLO_DATASET_DIR, "generate YOLO txt labels preserving original class ids") + + if job_type == "dataset.yolo_txt_sort": + return CommandSpec(conda_python(conda_env, YOLO_DATASET_DIR / "2_Check_and_Gen_Txt_Label_sort_label.py"), YOLO_DATASET_DIR, "generate YOLO txt labels with sorted class ids") + + if job_type == "dataset.yolo_convert_png": + args = conda_python(conda_env, YOLO_DATASET_DIR / "Tool_convert_bmp_jpg_to_png.py", required(params, "folder")) + if params.get("delete_source"): + args.append("--delete-source") + append_flag(args, "--workers", params.get("workers")) + return CommandSpec(args, YOLO_DATASET_DIR, "convert YOLO dataset images to PNG") + + if job_type == "dataset.yolo_resize": + args = conda_python(conda_env, YOLO_DATASET_DIR / "Tool_resize_pics.py", required(params, "folder")) + append_flag(args, "--size", params.get("size")) + append_flag(args, "--workers", params.get("workers")) + return CommandSpec(args, YOLO_DATASET_DIR, "resize YOLO dataset images recursively") + + return None diff --git a/backend/app/modules/mmseg/tasks.py b/backend/app/modules/mmseg/tasks.py index abbb2c8..699ea2d 100644 --- a/backend/app/modules/mmseg/tasks.py +++ b/backend/app/modules/mmseg/tasks.py @@ -1,6 +1,6 @@ from __future__ import annotations -from ...commands import CommandSpec, append_flag, conda_python, required +from ...commands import CommandSpec, append_flag, bash, conda_python, required from ...config import settings @@ -49,6 +49,12 @@ def build_mmseg_task(job_type: str, params: dict, conda_env: str) -> CommandSpec if job_type == "mmseg.generate_data": return CommandSpec(conda_python(conda_env, MY_DIR / "1_Initial_Data_All_data_from_1_Data_Parameter-V2.py"), MMSEG_DIR, "generate MMSeg dataset configs from JSON parameters") + if job_type == "mmseg.generate_data_v1": + return CommandSpec(conda_python(conda_env, MY_DIR / "1_Initial_Data_All_data_from_1_Data_Parameter-V1.py"), MMSEG_DIR, "generate MMSeg dataset configs with V1 flow") + + if job_type == "mmseg.generate_data_legacy": + return CommandSpec(conda_python(conda_env, MY_DIR / "1_Initial_Data_All-ori.py"), MMSEG_DIR, "run legacy original MMSeg dataset config generator") + if job_type == "mmseg.generate_alg": script = MY_DIR / "2_Initial_Alg_All_data_from_2_Alg_Program-V2.py" return CommandSpec( @@ -58,6 +64,18 @@ def build_mmseg_task(job_type: str, params: dict, conda_env: str) -> CommandSpec stdin_text=_stdin_for_generate_alg(params), ) + if job_type == "mmseg.generate_alg_v1": + script = MY_DIR / "2_Initial_Alg_All_data_from_2_Alg_Program-V1.py" + return CommandSpec( + conda_python(conda_env, script), + MMSEG_DIR, + "generate MMSeg algorithm config and training command with V1 flow", + stdin_text=_stdin_for_generate_alg(params), + ) + + if job_type == "mmseg.generate_alg_legacy": + return CommandSpec(conda_python(conda_env, MY_DIR / "2_Initial_Alg_All-ori-old.py"), MMSEG_DIR, "run legacy original MMSeg algorithm config generator") + if job_type == "mmseg.train": config_path = required(params, "config") args = conda_python(conda_env, MMSEG_DIR / "tools" / "train.py", config_path) @@ -71,6 +89,11 @@ def build_mmseg_task(job_type: str, params: dict, conda_env: str) -> CommandSpec stdin = f"{params.get('dataset_choice', 1)}\n{params.get('algorithm_choice', 0)}\n" return CommandSpec(args, MMSEG_DIR, "extract best MMSeg metrics from logs", stdin_text=stdin) + if job_type == "mmseg.metrics_v1": + args = conda_python(conda_env, MY_DIR / "4_2_predict_matrics_from_log_V1.py") + stdin = f"{params.get('dataset_choice', 1)}\n{params.get('algorithm_choice', 0)}\n" + return CommandSpec(args, MMSEG_DIR, "extract best MMSeg metrics from logs with V1 script", stdin_text=stdin) + if job_type == "mmseg.flops_fps": args = conda_python(conda_env, MY_DIR / "4_1_predict_params_FLOPs_FPS_V2.py") append_flag(args, "--input_dir", params.get("input_dir", "../Hardisk")) @@ -81,6 +104,11 @@ def build_mmseg_task(job_type: str, params: dict, conda_env: str) -> CommandSpec stdin += f"{params['shape_h']}\n{params['shape_w']}\n" return CommandSpec(args, MMSEG_DIR, "calculate MMSeg FLOPs/Params/FPS", stdin_text=stdin) + if job_type == "mmseg.flops_fps_v1": + args = conda_python(conda_env, MY_DIR / "4_1_predict_params_and_FLOPs_V1.py") + stdin = f"{params.get('dataset_choice', 1)}\n{params.get('algorithm_choice', 0)}\n" + return CommandSpec(args, MMSEG_DIR, "calculate MMSeg FLOPs/Params/FPS with V1 script", stdin_text=stdin) + if job_type == "mmseg.draw": return CommandSpec(conda_python(conda_env, MY_DIR / "4_3_predict_draw_pictures_and_tabels.py"), MMSEG_DIR, "generate MMSeg prediction pictures and tables") @@ -90,5 +118,13 @@ def build_mmseg_task(job_type: str, params: dict, conda_env: str) -> CommandSpec if job_type == "mmseg.delete_epoch": return CommandSpec(conda_python(conda_env, MY_DIR / "3_Find_And_Delete_Special_Epoch.py"), MMSEG_DIR, "find and delete selected epoch checkpoints") - return None + if job_type == "mmseg.copy_result": + return CommandSpec(bash(MY_DIR / "3_Tool_Copy_Result_To_Hardisk.sh"), MMSEG_DIR, "copy MMSeg results to Hardisk") + if job_type == "mmseg.predict_v1": + return CommandSpec(conda_python(conda_env, MY_DIR / "x4_Predict_V1-.py"), MMSEG_DIR, "run MMSeg prediction V1 helper") + + if job_type == "mmseg.predict_v2": + return CommandSpec(conda_python(conda_env, MY_DIR / "x4_Predict_V2-.py"), MMSEG_DIR, "run MMSeg prediction V2 helper") + + return None diff --git a/backend/app/modules/segmodel/tasks.py b/backend/app/modules/segmodel/tasks.py index b006d74..e6a7d8d 100644 --- a/backend/app/modules/segmodel/tasks.py +++ b/backend/app/modules/segmodel/tasks.py @@ -31,11 +31,39 @@ def build_segmodel_task(job_type: str, params: dict, conda_env: str) -> CommandS script = SEGMODEL_DIR / params.get("script", "2_predict_params_and_FLOPs_V2.py") return CommandSpec(conda_python(conda_env, script), SEGMODEL_DIR, "calculate SegModel params/FLOPs/FPS") + if job_type == "segmodel.params_flops": + args = conda_python(conda_env, SEGMODEL_DIR / "Tool_get_params_and_FLOPs.py") + append_flag(args, "-a", required(params, "architecture")) + shape = params.get("shape", [512, 512]) + if isinstance(shape, str): + shape = [part for part in shape.replace(",", " ").split() if part] + if shape: + args.append("--shape") + args.extend(str(item) for item in shape) + return CommandSpec(args, SEGMODEL_DIR, "calculate SegModel params and FLOPs for one architecture") + + if job_type == "segmodel.benchmark": + args = conda_python(conda_env, SEGMODEL_DIR / "Tool_benchmark_smp.py") + append_flag(args, "-a", required(params, "architecture")) + append_flag(args, "-c", params.get("checkpoint")) + shape = params.get("shape", [512, 512]) + if isinstance(shape, str): + shape = [part for part in shape.replace(",", " ").split() if part] + if shape: + args.append("--shape") + args.extend(str(item) for item in shape) + append_flag(args, "--repeat-times", params.get("repeat_times")) + append_flag(args, "--log-interval", params.get("log_interval")) + stdin = None if params.get("checkpoint") else params.get("stdin_text") + return CommandSpec(args, SEGMODEL_DIR, "benchmark SegModel FPS/latency", stdin_text=stdin) + if job_type == "segmodel.raw_mask_check": return CommandSpec(conda_python(conda_env, SEGMODEL_DIR / "1_predict_raw_masks_check.py"), SEGMODEL_DIR, "check SegModel raw mask completeness") if job_type == "segmodel.metrics": return CommandSpec(conda_python(conda_env, SEGMODEL_DIR / "3_predict_matrics_from_log.py"), SEGMODEL_DIR, "parse SegModel training/prediction metrics") - return None + if job_type == "segmodel.copy_best": + return CommandSpec(bash(SEGMODEL_DIR / "Tool_Copy_Best_Model.sh"), SEGMODEL_DIR, "copy best SegModel weights to prediction area") + return None diff --git a/backend/app/modules/system/tasks.py b/backend/app/modules/system/tasks.py index b4e60d5..0bb39b3 100644 --- a/backend/app/modules/system/tasks.py +++ b/backend/app/modules/system/tasks.py @@ -7,8 +7,9 @@ from ...config import settings def build_system_task(job_type: str, params: dict, conda_env: str) -> CommandSpec | None: if job_type == "system.backup": return CommandSpec(bash(settings.source_root / "Back_Up.sh"), settings.source_root, "run legacy backup script") + if job_type == "system.check_graph_card": + return CommandSpec(bash(settings.source_root / "Check_Graph_Card.sh"), settings.source_root, "run legacy GPU card detection script") if job_type == "mock.echo": message = params.get("message", "Seg Data Server mock job") return CommandSpec(["python", "-c", f"print({message!r})"], settings.project_root, "test job runner") return None - diff --git a/backend/app/modules/visual/__init__.py b/backend/app/modules/visual/__init__.py new file mode 100644 index 0000000..af1f474 --- /dev/null +++ b/backend/app/modules/visual/__init__.py @@ -0,0 +1,2 @@ +"""Visual tooling task wrappers.""" + diff --git a/backend/app/modules/visual/tasks.py b/backend/app/modules/visual/tasks.py new file mode 100644 index 0000000..ad8f9f5 --- /dev/null +++ b/backend/app/modules/visual/tasks.py @@ -0,0 +1,59 @@ +from __future__ import annotations + +from ...commands import CommandSpec, append_flag, conda_python +from ...config import settings +from ..dataset.tasks import _append_label_rebuild_flags + + +VISUAL_DIR = settings.source_root / "Tool-可视化" +VISUAL_LABEL_DIR = VISUAL_DIR / "0_图片Labels生成" + + +def build_visual_task(job_type: str, params: dict, conda_env: str) -> CommandSpec | None: + if job_type == "visual.train": + return CommandSpec(conda_python(conda_env, VISUAL_DIR / "train.py"), VISUAL_DIR, "run standalone YOLO visual training script") + + if job_type == "visual.inference": + return CommandSpec(conda_python(conda_env, VISUAL_DIR / "inference.py"), VISUAL_DIR, "run standalone YOLO visual inference script") + + if job_type == "visual.fps": + args = conda_python(conda_env, VISUAL_DIR / "get_FPS.py") + append_flag(args, "--weights", params.get("weights", "yolov8n.pt")) + append_flag(args, "--batch", params.get("batch", 1)) + imgs = params.get("imgs", [640, 640]) + if isinstance(imgs, str): + imgs = [part for part in imgs.replace(",", " ").split() if part] + if imgs: + args.append("--imgs") + args.extend(str(item) for item in imgs) + append_flag(args, "--device", params.get("device", "")) + append_flag(args, "--warmup", params.get("warmup")) + append_flag(args, "--testtime", params.get("testtime")) + if params.get("half"): + args.append("--half") + return CommandSpec(args, VISUAL_DIR, "measure YOLO visual model FPS") + + if job_type == "visual.yolo11_heatmap_v1": + return CommandSpec(conda_python(conda_env, VISUAL_DIR / "yolov11_heatmap_V1.py"), VISUAL_DIR, "run standalone YOLOv11 heatmap V1") + + if job_type == "visual.yolo11_heatmap_v2": + return CommandSpec(conda_python(conda_env, VISUAL_DIR / "yolov11_heatmap_V2.py"), VISUAL_DIR, "run standalone YOLOv11 heatmap V2") + + if job_type == "visual.label_ori": + return CommandSpec(conda_python(conda_env, VISUAL_DIR / "Tool_Check_and_Gen_Txt_Label_ori_label.py"), VISUAL_DIR, "generate YOLO txt labels from original label ids") + + if job_type == "visual.label_sort": + return CommandSpec(conda_python(conda_env, VISUAL_DIR / "Tool_Check_and_Gen_Txt_Label_sort_label.py"), VISUAL_DIR, "generate YOLO txt labels from sorted label ids") + + if job_type == "visual.gen_8bit_png": + return CommandSpec(conda_python(conda_env, VISUAL_DIR / "Tool_Gen_8_Bit_PNG[没用,不认].py"), VISUAL_DIR, "generate 8-bit PNG labels with legacy visual tool") + + if job_type == "visual.deal_labels": + args = conda_python(conda_env, VISUAL_LABEL_DIR / "4_deal_labels.py") + _append_label_rebuild_flags(args, params) + return CommandSpec(args, VISUAL_LABEL_DIR, "run visual label rebuild/conversion script") + + if job_type == "visual.tool_deal_labels_demo": + return CommandSpec(conda_python(conda_env, VISUAL_LABEL_DIR / "Tool_deal_labels.py"), VISUAL_LABEL_DIR, "run visual label helper demo script") + + return None diff --git a/backend/app/modules/yolo/tasks.py b/backend/app/modules/yolo/tasks.py index ff1ad71..972bc96 100644 --- a/backend/app/modules/yolo/tasks.py +++ b/backend/app/modules/yolo/tasks.py @@ -28,6 +28,15 @@ def build_yolo_task(job_type: str, params: dict, conda_env: str) -> CommandSpec choice = str(params.get("run_choice", 1)) return CommandSpec(args, YOLO_DIR, "predict with one YOLO model", stdin_text=f"{choice}\n") + if job_type == "yolo.predict_v1": + args = conda_python(conda_env, YOLO_DIR / "yolo_predict_V1.py") + append_flag(args, "--model", required(params, "model")) + append_flag(args, "--source", params.get("source")) + append_flag(args, "--pt_name", params.get("pt_name", "best.pt")) + append_flag(args, "--conf", params.get("conf", 0.2)) + choice = str(params.get("run_choice", 1)) + return CommandSpec(args, YOLO_DIR, "predict with legacy YOLO V1 script", stdin_text=f"{choice}\n") + if job_type == "yolo.batch_predict": args = bash(YOLO_DIR / "yolo_predict.sh") append_flag(args, "--pt_name", params.get("pt_name", "best.pt")) @@ -65,5 +74,7 @@ def build_yolo_task(job_type: str, params: dict, conda_env: str) -> CommandSpec if job_type == "yolo.video_unvisible": return CommandSpec(conda_python(conda_env, VIDEO_YOLO_DIR / "yolo_Seg_Video-V2-UnVisible.py"), VIDEO_YOLO_DIR, "render invisible/headless YOLO video prediction") - return None + if job_type == "yolo.layer_tester": + return CommandSpec(conda_python(conda_env, YOLO_DIR / "Yolo可视化测试" / "yolo_layer_tester.py"), YOLO_DIR, "test YOLO heatmap target layers") + return None diff --git a/backend/tests/test_agents.py b/backend/tests/test_agents.py index d8982d0..26f76a7 100644 --- a/backend/tests/test_agents.py +++ b/backend/tests/test_agents.py @@ -8,7 +8,8 @@ def test_evaluation_agent_returns_checks(): assert result["checks"] -def test_validation_agent_lightweight(): +def test_validation_agent_lightweight(monkeypatch): + monkeypatch.setenv("SEG_VALIDATE_LIVE", "0") result = validate_project(run_build=False) assert result["agent"] == "validation_agent" assert any(item["name"] == "catalog_has_yolo_heatmap" for item in result["checks"]) diff --git a/backend/tests/test_coverage.py b/backend/tests/test_coverage.py new file mode 100644 index 0000000..1ccbc9a --- /dev/null +++ b/backend/tests/test_coverage.py @@ -0,0 +1,8 @@ +from app.coverage import get_coverage_report + + +def test_coverage_report_maps_user_scripts_and_builds_tasks(): + report = get_coverage_report() + assert report["mapped_user_scripts"] == report["user_scripts_total"] + assert report["unmapped_user_scripts"] == [] + assert report["task_build_passed"] is True diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 457842b..1334b5b 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -4,6 +4,7 @@ import { Activity, BarChart3, Boxes, + ClipboardCheck, Cpu, Database, FileImage, @@ -38,6 +39,7 @@ type Job = { type Catalog = { task_types: string[]; + task_defaults: Record>; segmodel_architectures: string[]; yolo_models: string[]; mmseg_algorithms: string[]; @@ -61,6 +63,15 @@ type ResultItem = { kind: string; }; +type CoveragePayload = { + scripts_total: number; + user_scripts_total: number; + mapped_user_scripts: number; + unmapped_user_scripts: string[]; + task_build_passed: boolean; + task_build_checks: Array<{ task: string; passed: boolean; script_exists?: boolean; error?: string }>; +}; + type GpuPayload = { available: boolean; gpus: Array<{ @@ -85,13 +96,13 @@ async function api(path: string, init?: RequestInit): Promise { const defaultParams: Record> = { "mock.echo": { message: "hello from Seg Data Server" }, - "dataset.rename": { input_dir: "../DataSet_Own", prefix: "image" }, - "dataset.to_png": { input_dir: "../DataSet_Own", output_dir: "../DataSet_Own_png" }, - "dataset.resize": { input_dir: "../DataSet_Own", output_dir: "../DataSet_Own_resize", size: "512x512" }, - "dataset.pair": { image_dir: "../DataSet_Own/images", label_dir: "../DataSet_Own/labels" }, - "dataset.rebuild_labels": { label_dir: "../DataSet_Own/labels", output_dir: "../DataSet_Own/rebuilt_labels" }, - "dataset.stack": { image_dir: "../DataSet_Own/images", mask_dir: "../DataSet_Own/masks", output_dir: "../DataSet_Own/stacked" }, - "dataset.stitch": { input_dir: "../DataSet_Own/stacked", output_dir: "../DataSet_Own/stitch" }, + "dataset.rename": { image_dir: "../DataSet_Own/ORI", label_dir: "../DataSet_Own/Label" }, + "dataset.to_png": { input_dir: "../DataSet_Own/ORI", output_dir: "../DataSet_Own/ORI_PNG" }, + "dataset.resize": { image_dir: "../DataSet_Own/ORI", label_dir: "../DataSet_Own/Label", width: 1920, height: 1080 }, + "dataset.pair": { image_dir: "../DataSet_Own/ORI", label_dir: "../DataSet_Own/Label" }, + "dataset.rebuild_labels": { label_dir: "../DataSet_Own/Label" }, + "dataset.stack": { image_dir: "../DataSet_Own/ORI", label_dir: "../DataSet_Own/Label", result_dir: "../DataSet_Own/stacked", alpha: 0.3 }, + "dataset.stitch": { image_dir: "../DataSet_Own/ORI", label_dir: "../DataSet_Own/Label", result_dir: "../DataSet_Own/stitch" }, "dataset.video_frames": { video: "../Seg_Predict_Own_Video_V2/LC_Video_1.mp4", interval: 0.5, resize: "1920x1080" }, "segmodel.train": { architecture: "Unet" }, "segmodel.predict": { architecture: "Unet", run_choice: 1 }, @@ -113,7 +124,13 @@ const taskLabels: Record = { "dataset.rebuild_labels": "重建 Label", "dataset.stack": "透明叠加", "dataset.stitch": "拼接检查", - "dataset.video_frames": "视频抽帧" + "dataset.video_frames": "视频抽帧", + "dataset.yolo_check_pairs": "YOLO 配对", + "dataset.yolo_stack": "YOLO 叠加", + "dataset.yolo_rebuild_labels": "YOLO Label", + "dataset.yolo_txt_sort": "生成 TXT", + "dataset.yolo_convert_png": "批量 PNG", + "dataset.yolo_resize": "批量缩放" }; function formatBytes(value?: number) { @@ -134,22 +151,25 @@ function useData() { const [jobs, setJobs] = useState([]); const [results, setResults] = useState([]); const [datasets, setDatasets] = useState([]); + const [coverage, setCoverage] = useState(null); const [error, setError] = useState(""); async function refresh() { try { - const [catalogNext, gpusNext, jobsNext, resultsNext, datasetsNext] = await Promise.all([ + const [catalogNext, gpusNext, jobsNext, resultsNext, datasetsNext, coverageNext] = await Promise.all([ api("/api/catalog"), api("/api/system/gpus"), api("/api/jobs"), api("/api/results"), - api("/api/datasets") + api("/api/datasets"), + api("/api/coverage") ]); setCatalog(catalogNext); setGpus(gpusNext); setJobs(jobsNext); setResults(resultsNext.slice(0, 80)); setDatasets(datasetsNext); + setCoverage(coverageNext); setError(""); } catch (err) { setError(String(err)); @@ -162,7 +182,7 @@ function useData() { return () => window.clearInterval(timer); }, []); - return { catalog, gpus, jobs, results, datasets, error, refresh }; + return { catalog, gpus, jobs, results, datasets, coverage, error, refresh }; } function StatusPill({ status }: { status: string }) { @@ -170,7 +190,7 @@ function StatusPill({ status }: { status: string }) { } function App() { - const { catalog, gpus, jobs, results, datasets, error, refresh } = useData(); + const { catalog, gpus, jobs, results, datasets, coverage, error, refresh } = useData(); const [taskType, setTaskType] = useState("mock.echo"); const [params, setParams] = useState(JSON.stringify(defaultParams["mock.echo"], null, 2)); const [selectedJob, setSelectedJob] = useState(null); @@ -191,6 +211,7 @@ function App() { dataset: items.filter((task) => task.startsWith("dataset.")), segmodel: items.filter((task) => task.startsWith("segmodel.")), yolo: items.filter((task) => task.startsWith("yolo.")), + visual: items.filter((task) => task.startsWith("visual.")), mmseg: items.filter((task) => task.startsWith("mmseg.")), analysis: items.filter((task) => task.startsWith("analysis.") || task.startsWith("system.") || task.startsWith("mock.")) }; @@ -200,7 +221,7 @@ function App() { function pickTask(next: string) { setTaskType(next); - setParams(JSON.stringify(defaultParams[next] ?? {}, null, 2)); + setParams(JSON.stringify(catalog?.task_defaults?.[next] ?? defaultParams[next] ?? {}, null, 2)); } function pickDatasetTask(next: string) { @@ -297,6 +318,7 @@ function App() { 任务 数据集 GPU + 覆盖 权重 结果 @@ -336,6 +358,11 @@ function App() { 上传集 {datasets.length} +
+ + 任务 + {catalog?.task_types.length ?? 0} +
@@ -463,6 +490,57 @@ function App() {
+
+
+
+
+

Coverage

+

Seg 功能覆盖

+
+ +
+
+
+ 业务脚本 + {coverage?.mapped_user_scripts ?? 0}/{coverage?.user_scripts_total ?? 0} +
+
+ 全部脚本 + {coverage?.scripts_total ?? 0} +
+
+ 任务构建 + {coverage?.task_build_passed ? "OK" : "Check"} +
+
+
+ {(coverage?.unmapped_user_scripts.length ?? 0) === 0 ? ( + 当前用户侧脚本已全部映射到网页任务。 + ) : ( + coverage?.unmapped_user_scripts.slice(0, 8).map((item) => {item}) + )} +
+
+ +
+
+
+

Buildability

+

任务可启动检查

+
+ +
+
+ {(coverage?.task_build_checks ?? []).slice(0, 28).map((item) => ( +
+ {item.task} + {item.passed ? "command ready" : item.error ?? "check failed"} +
+ ))} +
+
+
+
diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 3c2bb5b..6a21fb6 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -182,7 +182,7 @@ h2 { } .metrics { - grid-template-columns: repeat(4, minmax(0, 1fr)); + grid-template-columns: repeat(5, minmax(0, 1fr)); margin-bottom: 16px; } @@ -233,7 +233,7 @@ h2 { .taskColumns { display: grid; - grid-template-columns: repeat(5, minmax(0, 1fr)); + grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 10px; } @@ -397,6 +397,85 @@ textarea { overflow: auto; } +.coverageGrid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 10px; + margin-bottom: 14px; +} + +.coverageGrid div { + min-width: 0; + padding: 14px; + border: 1px solid var(--line); + border-radius: 7px; + background: #101310; +} + +.coverageGrid span, +.coverageGrid strong { + display: block; +} + +.coverageGrid span { + color: var(--muted); + font-size: 12px; +} + +.coverageGrid strong { + margin-top: 6px; + font-size: 24px; +} + +.coverageStatus { + display: grid; + gap: 8px; + color: var(--muted); +} + +.coverageStatus code { + min-width: 0; + padding: 8px; + border-radius: 5px; + border: 1px solid var(--line); + background: #080a08; + color: var(--ink); + white-space: normal; + overflow-wrap: anywhere; +} + +.taskCheckList { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 8px; + max-height: 280px; + overflow: auto; +} + +.taskCheckList div { + min-width: 0; + padding: 9px; + border-radius: 6px; + border: 1px solid var(--line); + background: #101310; +} + +.taskCheckList div.ok { + border-color: rgba(157, 226, 111, 0.32); +} + +.taskCheckList div.bad { + border-color: rgba(240, 113, 103, 0.55); +} + +.taskCheckList span, +.taskCheckList small { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + .datasetCard { padding: 12px; border: 1px solid var(--line); @@ -589,8 +668,11 @@ meter { @media (max-width: 1180px) { body { min-width: 960px; } .shell { grid-template-columns: 220px 1fr; } + .metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); } .taskColumns { grid-template-columns: repeat(3, minmax(0, 1fr)); } .opGrid { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .coverageGrid, + .taskCheckList { grid-template-columns: 1fr; } .grid.three { grid-template-columns: 1fr; } .grid.two { grid-template-columns: 1fr; } }