fix: save calibration with config action

This commit is contained in:
Yoilun
2026-04-27 11:35:31 +08:00
parent c4f9dab049
commit 5b747bd1d8
3 changed files with 50 additions and 27 deletions

View File

@@ -58,18 +58,30 @@ def merge_calibration(
trash_roi: list[list[float]] | None, trash_roi: list[list[float]] | None,
) -> dict[str, Any]: ) -> dict[str, Any]:
merged = deepcopy(data) merged = deepcopy(data)
valid_zones = [] valid_zones: dict[str, dict[str, Any]] = {}
for zone in zones: for zone in zones:
zone_id = str(zone.get("id", "")).strip() zone_id = str(zone.get("id", "")).strip()
polygon = _normalize_points(zone.get("polygon", [])) polygon = _normalize_points(zone.get("polygon", []))
if not zone_id or len(polygon) < 3: if not zone_id or len(polygon) < 3:
continue continue
valid_zones.append({"id": zone_id, "polygon": polygon}) valid_zones[zone_id] = {"id": zone_id, "polygon": polygon}
if valid_zones: if valid_zones:
merged["zones"] = valid_zones existing_by_id = {
str(zone.get("id", "")).strip(): zone
for zone in merged.get("zones", [])
if str(zone.get("id", "")).strip()
}
existing_by_id.update(valid_zones)
layout = merged.setdefault("layout", {}) layout = merged.setdefault("layout", {})
layout["zone_ids"] = [zone["id"] for zone in valid_zones] zone_order = [str(item) for item in layout.get("zone_ids", []) if str(item) in existing_by_id]
for zone_id in valid_zones:
if zone_id not in zone_order:
zone_order.append(zone_id)
if not zone_order:
zone_order = list(valid_zones)
layout["zone_ids"] = zone_order
merged["zones"] = [existing_by_id[zone_id] for zone_id in zone_order if zone_id in existing_by_id]
if trash_roi is not None: if trash_roi is not None:
normalized_roi = _normalize_points(trash_roi) normalized_roi = _normalize_points(trash_roi)

View File

@@ -13,8 +13,8 @@ class ManageApiTests(unittest.TestCase):
def test_merge_calibration_updates_zones_and_trash(self) -> None: def test_merge_calibration_updates_zones_and_trash(self) -> None:
data = { data = {
"camera_id": "cam", "camera_id": "cam",
"layout": {"rows": 2, "cols": 4, "zone_ids": ["r1c1"]}, "layout": {"rows": 2, "cols": 4, "zone_ids": ["r1c1", "r1c2"]},
"zones": [], "zones": [{"id": "r1c2", "polygon": [[0.5, 0], [1, 0], [1, 0.5]]}],
} }
merged = merge_calibration( merged = merge_calibration(
@@ -23,8 +23,9 @@ class ManageApiTests(unittest.TestCase):
[[0.8, 0.8], [1, 0.8], [1, 1], [0.8, 1]], [[0.8, 0.8], [1, 0.8], [1, 1], [0.8, 1]],
) )
self.assertEqual(merged["layout"]["zone_ids"], ["r1c1"]) self.assertEqual(merged["layout"]["zone_ids"], ["r1c1", "r1c2"])
self.assertEqual(merged["zones"][0]["id"], "r1c1") self.assertEqual(merged["zones"][0]["id"], "r1c1")
self.assertEqual(merged["zones"][1]["id"], "r1c2")
self.assertEqual(merged["trash"]["roi"][0], [0.8, 0.8]) self.assertEqual(merged["trash"]["roi"][0], [0.8, 0.8])
def test_save_config_document_round_trips_manage_fields(self) -> None: def test_save_config_document_round_trips_manage_fields(self) -> None:

View File

@@ -57,9 +57,9 @@ app.innerHTML = `
RTSP 地址 RTSP 地址
<input id="rtspUrl" type="text" placeholder="rtsp://user:password@camera-ip:554/stream"> <input id="rtspUrl" type="text" placeholder="rtsp://user:password@camera-ip:554/stream">
</label> </label>
<button id="saveConfig" type="button">保存配置</button> <button id="saveConfig" type="button">保存配置和标定</button>
<button id="captureSnapshot" type="button">抓取一帧</button> <button id="captureSnapshot" type="button">抓取一帧</button>
<button id="saveCalibration" type="button">保存标定到配置</button> <button id="saveCalibration" type="button">保存标定</button>
</section> </section>
<section class="calibration-grid"> <section class="calibration-grid">
@@ -184,9 +184,10 @@ async function saveConfig() {
}, },
}; };
state.config = await apiJson("/api/manage/config", {method: "PUT", body: payload}); state.config = await apiJson("/api/manage/config", {method: "PUT", body: payload});
const calibrationSaved = await persistCalibration({requireAny: false});
fillForm(); fillForm();
renderConfigPreview(); renderConfigPreview();
setStatus("配置已保存"); setStatus(calibrationSaved ? "配置和标定已保存" : "配置已保存;当前没有可保存的标定点");
} catch (error) { } catch (error) {
setStatus(`保存配置失败:${error.message}`); setStatus(`保存配置失败:${error.message}`);
} }
@@ -225,29 +226,38 @@ async function captureSnapshot() {
async function saveCalibration() { async function saveCalibration() {
try { try {
const zones = zoneIds const saved = await persistCalibration({requireAny: true});
.map((id) => ({id, polygon: state.polygons[id]})) if (saved) {
.filter((zone) => zone.polygon.length >= 3); render();
const trashPolygon = state.polygons.trash; setStatus("标定已保存到项目配置");
if (zones.length !== zoneIds.length) {
setStatus("8 个格口都标定后才能保存");
return;
} }
if (trashPolygon.length < 3) {
setStatus("垃圾桶区域至少需要 3 个点");
return;
}
state.config = await apiJson("/api/manage/calibration", {
method: "PUT",
body: {zones, trash: {roi: trashPolygon}},
});
render();
setStatus("标定已保存到项目配置");
} catch (error) { } catch (error) {
setStatus(`保存标定失败:${error.message}`); setStatus(`保存标定失败:${error.message}`);
} }
} }
async function persistCalibration({requireAny}) {
const zones = zoneIds
.map((id) => ({id, polygon: state.polygons[id]}))
.filter((zone) => zone.polygon.length >= 3);
const trashPolygon = state.polygons.trash;
const payload = {zones, trash: {}};
if (trashPolygon.length >= 3) {
payload.trash.roi = trashPolygon;
}
if (!zones.length && !payload.trash.roi) {
if (requireAny) {
setStatus("当前没有可保存的标定点;每个区域至少需要 3 个点");
}
return false;
}
state.config = await apiJson("/api/manage/calibration", {
method: "PUT",
body: payload,
});
return true;
}
function setTab(tab) { function setTab(tab) {
state.activeTab = tab; state.activeTab = tab;
document.querySelectorAll(".tabs button").forEach((button) => { document.querySelectorAll(".tabs button").forEach((button) => {