整合去雾网页工具
This commit is contained in:
265
web_dehaze/static/app.js
Normal file
265
web_dehaze/static/app.js
Normal file
@@ -0,0 +1,265 @@
|
||||
const state = {
|
||||
images: [],
|
||||
capabilities: null,
|
||||
selectedImage: null,
|
||||
currentJob: null,
|
||||
pollTimer: null,
|
||||
};
|
||||
|
||||
const $ = (id) => document.getElementById(id);
|
||||
|
||||
function assetUrl(path) {
|
||||
return `/asset?path=${encodeURIComponent(path)}&t=${Date.now()}`;
|
||||
}
|
||||
|
||||
async function api(path, options = {}) {
|
||||
const response = await fetch(path, options);
|
||||
const payload = await response.json();
|
||||
if (!response.ok) {
|
||||
throw new Error(payload.error || response.statusText);
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
function formatBytes(bytes) {
|
||||
if (!Number.isFinite(bytes)) return "";
|
||||
if (bytes < 1024) return `${bytes} B`;
|
||||
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
||||
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
||||
}
|
||||
|
||||
function renderImages() {
|
||||
const box = $("imageList");
|
||||
box.innerHTML = "";
|
||||
state.images.forEach((image) => {
|
||||
const item = document.createElement("button");
|
||||
item.className = `image-item ${state.selectedImage === image.name ? "active" : ""}`;
|
||||
item.type = "button";
|
||||
item.innerHTML = `
|
||||
<span>${image.name}</span>
|
||||
<span class="image-meta">${image.width || "?"}x${image.height || "?"} · ${formatBytes(image.size)}</span>
|
||||
`;
|
||||
item.addEventListener("click", () => selectImage(image.name));
|
||||
box.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
function renderMethods() {
|
||||
const box = $("methodList");
|
||||
box.innerHTML = "";
|
||||
const methods = state.capabilities?.methods || {};
|
||||
Object.entries(methods).forEach(([key, info]) => {
|
||||
const label = document.createElement("label");
|
||||
label.className = `check-item ${info.available ? "" : "disabled"}`;
|
||||
label.innerHTML = `
|
||||
<span>
|
||||
<strong>${info.label}</strong>
|
||||
<span class="method-meta">${info.available ? "ready" : `缺 ${info.module_ok ? "模型" : info.module}`}</span>
|
||||
</span>
|
||||
<input type="checkbox" value="${key}" ${info.available || key === "Baidu_API" || key === "DCP" ? "" : "disabled"} ${key === "DCP" ? "checked" : ""} />
|
||||
`;
|
||||
box.appendChild(label);
|
||||
});
|
||||
}
|
||||
|
||||
function renderPostprocessors() {
|
||||
const box = $("postList");
|
||||
box.innerHTML = "";
|
||||
const processors = state.capabilities?.postprocessors || {};
|
||||
Object.entries(processors).forEach(([key, info]) => {
|
||||
const label = document.createElement("label");
|
||||
label.className = "check-item";
|
||||
label.innerHTML = `
|
||||
<span><strong>${info.label}</strong></span>
|
||||
<input type="checkbox" value="${key}" ${key === "manual_sv" ? "checked" : ""} />
|
||||
`;
|
||||
box.appendChild(label);
|
||||
});
|
||||
}
|
||||
|
||||
function renderReferenceOptions() {
|
||||
const select = $("referenceImage");
|
||||
select.innerHTML = "";
|
||||
state.images.forEach((image) => {
|
||||
const option = document.createElement("option");
|
||||
option.value = image.name;
|
||||
option.textContent = image.name;
|
||||
option.selected = image.name === state.selectedImage;
|
||||
select.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
function setJobState(text, status = "") {
|
||||
const box = $("jobState");
|
||||
box.textContent = text;
|
||||
box.className = `job-state ${status}`;
|
||||
}
|
||||
|
||||
async function selectImage(name) {
|
||||
state.selectedImage = name;
|
||||
$("currentTitle").textContent = name;
|
||||
renderImages();
|
||||
renderReferenceOptions();
|
||||
await refreshResults();
|
||||
}
|
||||
|
||||
function resultCard(title, path, exists = true) {
|
||||
const card = document.createElement("article");
|
||||
card.className = "result-card";
|
||||
const badge = exists ? '<span class="badge ok">ready</span>' : '<span class="badge pending">未生成</span>';
|
||||
const body = exists
|
||||
? `<div class="image-frame"><img src="${assetUrl(path)}" alt="${title}" loading="lazy" /></div>`
|
||||
: '<div class="image-frame"><span class="missing">暂无结果</span></div>';
|
||||
card.innerHTML = `<header><h3>${title}</h3>${badge}</header>${body}`;
|
||||
return card;
|
||||
}
|
||||
|
||||
function updatePostSourceOptions(results) {
|
||||
const select = $("postSource");
|
||||
const previous = select.value;
|
||||
select.innerHTML = "";
|
||||
const original = document.createElement("option");
|
||||
original.value = "original";
|
||||
original.textContent = "原图";
|
||||
select.appendChild(original);
|
||||
results.dehaze.filter((item) => item.exists).forEach((item) => {
|
||||
const option = document.createElement("option");
|
||||
option.value = item.method;
|
||||
option.textContent = item.label;
|
||||
select.appendChild(option);
|
||||
});
|
||||
if ([...select.options].some((option) => option.value === previous)) {
|
||||
select.value = previous;
|
||||
} else if (results.dehaze.some((item) => item.method === "DCP" && item.exists)) {
|
||||
select.value = "DCP";
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshResults() {
|
||||
if (!state.selectedImage) return;
|
||||
const results = await api(`/api/results?image=${encodeURIComponent(state.selectedImage)}`);
|
||||
const grid = $("resultGrid");
|
||||
grid.innerHTML = "";
|
||||
grid.appendChild(resultCard(results.original.label, results.original.path, true));
|
||||
results.dehaze.forEach((item) => {
|
||||
grid.appendChild(resultCard(item.label, item.path, item.exists));
|
||||
});
|
||||
results.postprocess.forEach((item) => {
|
||||
grid.appendChild(resultCard(item.name, item.path, true));
|
||||
});
|
||||
updatePostSourceOptions(results);
|
||||
}
|
||||
|
||||
function selectedValues(containerId) {
|
||||
return [...$(containerId).querySelectorAll("input[type=checkbox]:checked")].map((input) => input.value);
|
||||
}
|
||||
|
||||
function postParams() {
|
||||
return {
|
||||
manual_sv: {
|
||||
s_gain: Number($("sGain").value),
|
||||
v_gain: Number($("vGain").value),
|
||||
},
|
||||
hsv_hist: {
|
||||
match_hue: $("matchHue").checked,
|
||||
},
|
||||
hist_auto_sv: {
|
||||
match_hue: $("matchHue").checked,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function startJob(endpoint, payload) {
|
||||
const response = await api(endpoint, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
state.currentJob = response.job.id;
|
||||
$("logBox").textContent = "";
|
||||
setJobState("Running", "running");
|
||||
pollJob();
|
||||
}
|
||||
|
||||
async function pollJob() {
|
||||
if (!state.currentJob) return;
|
||||
clearTimeout(state.pollTimer);
|
||||
try {
|
||||
const job = await api(`/api/job?id=${encodeURIComponent(state.currentJob)}`);
|
||||
$("logBox").textContent = (job.logs || []).join("\n");
|
||||
$("logBox").scrollTop = $("logBox").scrollHeight;
|
||||
if (job.status === "running") {
|
||||
setJobState("Running", "running");
|
||||
state.pollTimer = setTimeout(pollJob, 1000);
|
||||
} else {
|
||||
setJobState(job.status === "done" ? "Done" : "Error", job.status);
|
||||
await refreshResults();
|
||||
}
|
||||
} catch (error) {
|
||||
setJobState("Error", "error");
|
||||
$("logBox").textContent = error.message;
|
||||
}
|
||||
}
|
||||
|
||||
async function runSelectedModels() {
|
||||
if (!state.selectedImage) return;
|
||||
const methods = selectedValues("methodList");
|
||||
const payload = {
|
||||
image: state.selectedImage,
|
||||
methods,
|
||||
options: {
|
||||
DCP: {
|
||||
sz: Number($("dcpSz").value),
|
||||
tx: Number($("dcpTx").value),
|
||||
},
|
||||
},
|
||||
};
|
||||
await startJob("/api/run", payload);
|
||||
}
|
||||
|
||||
async function runPostprocess() {
|
||||
if (!state.selectedImage) return;
|
||||
const processors = selectedValues("postList");
|
||||
const payload = {
|
||||
image: state.selectedImage,
|
||||
source: $("postSource").value,
|
||||
reference: $("referenceImage").value || state.selectedImage,
|
||||
processors,
|
||||
params: postParams(),
|
||||
};
|
||||
await startJob("/api/postprocess", payload);
|
||||
}
|
||||
|
||||
function bindControls() {
|
||||
$("runBtn").addEventListener("click", runSelectedModels);
|
||||
$("postBtn").addEventListener("click", runPostprocess);
|
||||
$("refreshBtn").addEventListener("click", refreshResults);
|
||||
$("sGain").addEventListener("input", () => {
|
||||
$("sGainValue").textContent = `${Math.round(Number($("sGain").value) * 100)}%`;
|
||||
});
|
||||
$("vGain").addEventListener("input", () => {
|
||||
$("vGainValue").textContent = `${Math.round(Number($("vGain").value) * 100)}%`;
|
||||
});
|
||||
}
|
||||
|
||||
async function init() {
|
||||
bindControls();
|
||||
const [capabilities, images] = await Promise.all([api("/api/capabilities"), api("/api/images")]);
|
||||
state.capabilities = capabilities;
|
||||
state.images = images.images || [];
|
||||
$("envLine").textContent = capabilities.results_dir;
|
||||
renderMethods();
|
||||
renderPostprocessors();
|
||||
renderImages();
|
||||
if (state.images.length) {
|
||||
await selectImage(state.images[0].name);
|
||||
} else {
|
||||
$("currentTitle").textContent = "待去雾图片为空";
|
||||
}
|
||||
setJobState("Idle");
|
||||
}
|
||||
|
||||
init().catch((error) => {
|
||||
setJobState("Error", "error");
|
||||
$("logBox").textContent = error.stack || error.message;
|
||||
});
|
||||
Reference in New Issue
Block a user