Expand Seg task coverage and coverage dashboard

This commit is contained in:
2026-06-30 12:54:25 +08:00
parent dd7b7384ec
commit 7a43303f15
18 changed files with 849 additions and 28 deletions

View File

@@ -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<string, Record<string, unknown>>;
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<T>(path: string, init?: RequestInit): Promise<T> {
const defaultParams: Record<string, Record<string, unknown>> = {
"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<string, string> = {
"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<Job[]>([]);
const [results, setResults] = useState<ResultItem[]>([]);
const [datasets, setDatasets] = useState<UploadedDataset[]>([]);
const [coverage, setCoverage] = useState<CoveragePayload | null>(null);
const [error, setError] = useState<string>("");
async function refresh() {
try {
const [catalogNext, gpusNext, jobsNext, resultsNext, datasetsNext] = await Promise.all([
const [catalogNext, gpusNext, jobsNext, resultsNext, datasetsNext, coverageNext] = await Promise.all([
api<Catalog>("/api/catalog"),
api<GpuPayload>("/api/system/gpus"),
api<Job[]>("/api/jobs"),
api<ResultItem[]>("/api/results"),
api<UploadedDataset[]>("/api/datasets")
api<UploadedDataset[]>("/api/datasets"),
api<CoveragePayload>("/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<Job | null>(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() {
<a href="#jobs"><Terminal size={18} /></a>
<a href="#datasets"><Boxes size={18} /></a>
<a href="#gpu"><Cpu size={18} />GPU</a>
<a href="#coverage"><ClipboardCheck size={18} /></a>
<a href="#weights"><HardDrive size={18} /></a>
<a href="#results"><BarChart3 size={18} /></a>
</nav>
@@ -336,6 +358,11 @@ function App() {
<span></span>
<strong>{datasets.length}</strong>
</div>
<div className="metric">
<ClipboardCheck size={20} />
<span></span>
<strong>{catalog?.task_types.length ?? 0}</strong>
</div>
</section>
<section className="grid two" id="jobs">
@@ -463,6 +490,57 @@ function App() {
</div>
</section>
<section className="grid two" id="coverage">
<div className="panel">
<div className="panelHead">
<div>
<p className="eyebrow">Coverage</p>
<h2>Seg </h2>
</div>
<ClipboardCheck size={22} />
</div>
<div className="coverageGrid">
<div>
<span></span>
<strong>{coverage?.mapped_user_scripts ?? 0}/{coverage?.user_scripts_total ?? 0}</strong>
</div>
<div>
<span></span>
<strong>{coverage?.scripts_total ?? 0}</strong>
</div>
<div>
<span></span>
<strong>{coverage?.task_build_passed ? "OK" : "Check"}</strong>
</div>
</div>
<div className="coverageStatus">
{(coverage?.unmapped_user_scripts.length ?? 0) === 0 ? (
<span></span>
) : (
coverage?.unmapped_user_scripts.slice(0, 8).map((item) => <code key={item}>{item}</code>)
)}
</div>
</div>
<div className="panel">
<div className="panelHead">
<div>
<p className="eyebrow">Buildability</p>
<h2></h2>
</div>
<Terminal size={22} />
</div>
<div className="taskCheckList">
{(coverage?.task_build_checks ?? []).slice(0, 28).map((item) => (
<div key={item.task} className={item.passed ? "ok" : "bad"}>
<span>{item.task}</span>
<small>{item.passed ? "command ready" : item.error ?? "check failed"}</small>
</div>
))}
</div>
</div>
</section>
<section className="grid three">
<div className="panel" id="gpu">
<div className="panelHead">