From 559266d5e63dafcfd3afdad55909e1add97553d7 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 29 May 2026 01:46:52 +0800 Subject: [PATCH] Refine registration workspace controls --- DICOM_and_UPP配准/app.py | 3 + DICOM_and_UPP配准/static/app.js | 321 ++++++++++++++++++++++------ DICOM_and_UPP配准/static/index.html | 150 ++++++++----- DICOM_and_UPP配准/static/styles.css | 231 ++++++++++++++++++-- 4 files changed, 576 insertions(+), 129 deletions(-) diff --git a/DICOM_and_UPP配准/app.py b/DICOM_and_UPP配准/app.py index 6a4150f..91dec31 100644 --- a/DICOM_and_UPP配准/app.py +++ b/DICOM_and_UPP配准/app.py @@ -468,6 +468,7 @@ def cases( q: str = "", status_filter: str = Query(default="", alias="status"), body_part: str = "", + algorithm_model: str = "", limit: int = Query(default=120, ge=1, le=400), _: str = Depends(require_auth), ) -> list[dict[str, Any]]: @@ -492,6 +493,8 @@ def cases( clauses.append(f"registration_status = {sql_literal(status_filter)}") if body_part in {"head_neck", "chest", "upper_abdomen", "lower_abdomen", "pelvis"}: clauses.append(f"body_part_keys ? {sql_literal(body_part)}") + if algorithm_model.strip(): + clauses.append(f"algorithm_model ILIKE {sql_literal('%' + algorithm_model.strip().replace('%', '').replace('_', '') + '%')}") where_sql = "WHERE " + " AND ".join(clauses) if clauses else "" return pg_json_rows( f""" diff --git a/DICOM_and_UPP配准/static/app.js b/DICOM_and_UPP配准/static/app.js index 9bddb0b..b176082 100644 --- a/DICOM_and_UPP配准/static/app.js +++ b/DICOM_and_UPP配准/static/app.js @@ -46,6 +46,7 @@ const state = { links: { pacs_viewer_url: "http://127.0.0.1:8107", relation_viewer_url: "http://127.0.0.1:8108" }, statusFilter: "", partFilter: "", + modelFilter: "", search: "", caseCollapsed: false, activeToolTab: "series", @@ -60,11 +61,15 @@ const state = { pose: { ...DEFAULT_POSE }, windowMode: "default", fusionMode: "fusion", + fusionDetail: "low", + modelDetail: "standard", + mappingMode: "result", sliceIndex: 0, sliceRangeStart: 0, sliceRangeEnd: 0, dicomPreviewTimer: 0, autoResult: null, + lastAutoPose: null, dirty: false, dirtyReason: "", saving: false, @@ -77,6 +82,8 @@ const state = { scene: null, controls: null, dicomGroup: null, + dicomPlane: null, + dicomPoints: null, modelGroup: null, rawModelGroup: null, modelMeshes: [], @@ -247,6 +254,7 @@ async function loadCases(selectFirst = true) { if (state.search) params.set("q", state.search); if (state.statusFilter) params.set("status", state.statusFilter); if (state.partFilter) params.set("body_part", state.partFilter); + if (state.modelFilter) params.set("algorithm_model", state.modelFilter); const rows = await api(`/api/cases?${params.toString()}`); state.cases = rows; renderCases(); @@ -382,12 +390,6 @@ function clearDetail() { $("activeTitle").textContent = "未选择 CT"; $("activeMeta").textContent = "从左侧选择一个完整关联检查"; $("activeTags").innerHTML = ""; - $("patientSummary").textContent = "未选择 CT"; - $("summaryCt").textContent = "-"; - $("summaryPatientId").textContent = "-"; - $("summaryStudyTime").textContent = "-"; - $("summaryBodyPart").textContent = "-"; - $("matchedSeries").innerHTML = ""; $("seriesList").innerHTML = `
未选择 CT
`; $("stlList").innerHTML = `
未选择 STL
`; $("modelRail").innerHTML = ""; @@ -407,7 +409,7 @@ async function selectCase(ctNumber, algorithmModel = "未指定模型") { && sameCtNumber(state.activeCase.ct_number, ctNumber) && (state.algorithmModel || "未指定模型") !== normalizedAlgorithm; const previousPose = { ...state.pose }; - if (!switchingModelOnly && !(await confirmChange())) return; + if (!(await confirmChange())) return; setTopLoading(true); setFusionLoading(true, "正在读取 CT 关联数据", 6); try { @@ -426,7 +428,10 @@ async function selectCase(ctNumber, algorithmModel = "未指定模型") { const savedTransform = registration.transform || {}; const keepPreviousPose = switchingModelOnly; state.pose = keepPreviousPose ? { ...DEFAULT_POSE, ...previousPose } : { ...DEFAULT_POSE, ...savedTransform }; + state.fusionDetail = registration.model_reference?.fusion_detail || state.fusionDetail || "low"; + state.modelDetail = registration.model_reference?.model_detail || state.modelDetail || "standard"; state.autoResult = null; + state.lastAutoPose = null; state.sliceIndex = 0; $("notes").value = registration.notes || ""; @@ -447,6 +452,7 @@ async function selectCase(ctNumber, algorithmModel = "未指定模型") { renderSeries(); renderStl(); renderPoseControls(); + syncDisplayControls(); resetDirty(); await loadFusion(); } catch (error) { @@ -503,26 +509,25 @@ function currentSeries() { function renderActiveHeader() { const row = state.activeCase; if (!row) return; + const selected = currentSeries(); + const bodyTags = bodyPartTags(row); + const selectedLabels = selected?.annotation_labels?.length ? selected.annotation_labels.filter((label) => !String(label).includes("略过") && !String(label).includes("不采用")) : []; $("activeTitle").textContent = `${row.ct_number} · ${row.algorithm_model || "未指定模型"}`; - $("activeMeta").textContent = `${row.patient_name || row.upp_patient_name || "-"} · ${fmtDateTime(row.study_date, row.study_time) || "检查时间未知"} · ${Number(row.series_count || 0)} 个 DICOM 序列 · STL ${Number(row.stl_file_count || 0)} 个`; + $("activeMeta").textContent = `${row.patient_name || row.upp_patient_name || "-"} · ${row.patient_id || "-"} · ${fmtDateTime(row.study_date, row.study_time) || "检查时间未知"} · ${bodyTags.join("、") || row.study_description || "部位未知"} · ${Number(row.series_count || 0)} 个 DICOM 序列 · STL ${Number(row.stl_file_count || 0)} 个`; const tags = [ - `${statusText(state.registrationStatus)}`, - ...bodyPartTags(row).map((tag) => `${escapeHtml(tag)}`), + `${statusText(state.registrationStatus)}`, + `${escapeHtml(state.algorithmModel || row.algorithm_model || "未指定模型")}`, + ...(selected ? [`${escapeHtml(selected.description || "当前序列")}`] : []), + ...selectedLabels.map((tag) => `${escapeHtml(tag)}`), + ...bodyTags.map((tag) => `${escapeHtml(tag)}`), ]; $("activeTags").innerHTML = tags.join(""); $("statusBtn").textContent = state.registrationStatus === "registered" ? "标为未配准" : "标为已配准"; $("statusBtn").classList.toggle("unregistered", state.registrationStatus !== "registered"); - $("patientSummary").textContent = `${row.patient_name || row.upp_patient_name || "-"} ${row.patient_sex || ""}`.trim(); - $("summaryCt").textContent = row.ct_number || "-"; - $("summaryPatientId").textContent = row.patient_id || "-"; - $("summaryStudyTime").textContent = fmtDateTime(row.study_date, row.study_time) || "-"; - $("summaryBodyPart").textContent = bodyPartTags(row).join("、") || row.study_description || "-"; } function renderSeries() { $("seriesCount").textContent = state.series.length; - const activeSeries = currentSeries(); - $("matchedSeries").innerHTML = activeSeries ? seriesCardHtml(activeSeries, true) : `
尚未选择配准序列
`; $("seriesList").innerHTML = state.series.length ? sortedSeriesForDisplay(state.series) .map((item) => seriesCardHtml(item, item.series_uid === state.selectedSeriesUid)) @@ -541,6 +546,7 @@ function seriesCardHtml(item, active = false) { const skipped = isSkippedSeries(item); return ` +
+ + + + +
@@ -90,27 +96,21 @@