Expand Seg task coverage and coverage dashboard
This commit is contained in:
@@ -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">
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user