Files
PACS/DICOM_and_UPP配准/static/index.html

424 lines
21 KiB
HTML

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>DICOM / UPP 配准工作台</title>
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='8' fill='%2319d6c3'/%3E%3Cpath d='M9 17h14M16 10v14' stroke='%233378f6' stroke-width='4' stroke-linecap='round'/%3E%3C/svg%3E" />
<link rel="stylesheet" href="/static/styles.css" />
<script type="importmap">
{
"imports": {
"three": "/static/vendor/three.module.min.js"
}
}
</script>
</head>
<body>
<div id="loginOverlay" class="login-overlay">
<form id="loginForm" class="login-panel">
<span class="brand-mark"></span>
<h1>DICOM / UPP 配准工作台</h1>
<p>登录后从完整关联 CT 中选择 DICOM 与 STL 配准关系</p>
<input id="username" autocomplete="username" placeholder="账号" />
<input id="password" type="password" autocomplete="current-password" placeholder="密码" />
<button type="submit">登录</button>
<span id="loginError"></span>
</form>
</div>
<header class="topbar">
<div class="brand">
<span class="brand-mark"></span>
<div>
<h1>DICOM / UPP 配准工作台</h1>
<p id="subtitle">完整关联 CT · STL 搭配 · 位姿参数</p>
</div>
</div>
<div class="top-actions">
<button id="relationBtn" class="ghost-btn accent" type="button">数据库关联可视化</button>
<button id="viewerBtn" class="ghost-btn" type="button">DICOM 阅片系统</button>
<span id="dbStatus" class="status-pill">数据库</span>
<button id="refreshBtn" class="ghost-btn" type="button">刷新</button>
<button id="settingsBtn" class="ghost-btn" type="button">设置</button>
<span id="userBadge" class="user-badge">未登录</span>
<button id="logoutBtn" class="ghost-btn" type="button">退出</button>
</div>
</header>
<div id="topLoading" class="top-loading hidden"><span></span></div>
<section id="settingsPanel" class="settings-panel hidden" aria-label="系统设置">
<div class="settings-shell">
<div class="settings-head">
<div>
<h2>系统设置</h2>
<p>DICOM / UPP 数据刷新与配准缓存</p>
</div>
<button id="settingsCloseBtn" class="ghost-btn" type="button">关闭</button>
</div>
<div class="settings-grid">
<section class="settings-card">
<div class="settings-card-head">
<h3>自动刷新</h3>
<label class="switch-line">
<input id="autoRefreshEnabled" type="checkbox" />
<span>启用</span>
</label>
</div>
<div class="settings-row">
<label for="refreshTime">每日刷新时间</label>
<input id="refreshTime" type="time" value="03:00" />
</div>
<div class="settings-actions">
<button id="saveSettingsBtn" class="primary-btn" type="button">保存设置</button>
<button id="manualCacheRefreshBtn" class="ghost-btn" type="button">立刻刷新缓存</button>
</div>
</section>
<section class="settings-card">
<div class="settings-card-head">
<h3>缓存状态</h3>
<span id="cacheStage" class="status-pill">未刷新</span>
</div>
<div class="cache-progress">
<span id="cacheProgressFill"></span>
</div>
<div id="cacheMessage" class="cache-message">等待刷新</div>
<div class="settings-metrics">
<span id="cacheCases">0 / 0 CT</span>
<span id="cacheFiles">STL 0</span>
<span id="cacheNextRun">下次 -</span>
</div>
<code id="cacheRootText" class="cache-root"></code>
</section>
</div>
</div>
</section>
<main class="workspace">
<aside class="case-panel panel">
<div class="panel-head">
<div class="panel-title-row">
<div>
<h2>完整关联 CT</h2>
<p>DICOM + STL</p>
</div>
<button id="caseToggleBtn" class="icon-btn" type="button" title="收起/展开完整关联 CT">
<span></span><span></span><span></span>
</button>
</div>
<span id="caseCount">0</span>
</div>
<input id="caseSearch" class="search-input" placeholder="搜索 CT号 / 姓名 / 模型" />
<div class="filter-row">
<button class="filter active" data-status="">全部</button>
<button class="filter" data-status="unregistered">未配准</button>
<button class="filter" data-status="registered">已配准</button>
</div>
<div class="part-filter">
<button class="filter active" data-part="">全部部位</button>
<button class="filter" data-part="head_neck">头颈部</button>
<button class="filter" data-part="chest">胸部</button>
<button class="filter" data-part="upper_abdomen">上腹部</button>
<button class="filter" data-part="abdomen_pelvis">腹盆部</button>
</div>
<div class="model-filter">
<button class="filter active" data-model-filter="">全部模型</button>
<button class="filter" data-model-filter="肝胆模型">肝胆模型</button>
<button class="filter" data-model-filter="泌尿模型">泌尿模型</button>
<button class="filter" data-model-filter="胸部模型">胸部模型</button>
</div>
<div id="caseList" class="case-list"></div>
</aside>
<section class="main-panel">
<section class="case-strip panel">
<div>
<h2 id="activeTitle">未选择 CT</h2>
<p id="activeMeta">从左侧选择一个完整关联检查</p>
</div>
<div id="activeTags" class="tag-line"></div>
</section>
<section class="registration-board">
<aside class="tool-dock panel">
<div class="tool-tabs">
<button class="active" data-tool-tab="series">序列</button>
<button data-tool-tab="models">模型可视</button>
<button data-tool-tab="pose">位姿</button>
</div>
<section class="tool-pane active" data-tool-pane="series">
<div class="display-segmented" id="fusionDetailControls">
<button data-fusion-detail="low" type="button">DICOM 低</button>
<button data-fusion-detail="medium" type="button">DICOM 中</button>
<button class="active" data-fusion-detail="high" type="button">DICOM 高</button>
</div>
<div class="tool-section-title">
<span>检查序列</span>
<em id="seriesCount">0</em>
</div>
<div id="seriesList" class="series-list compact-list"></div>
</section>
<section class="tool-pane" data-tool-pane="models">
<div class="display-segmented" id="modelDetailControls">
<button data-model-detail="standard" type="button">标准</button>
<button data-model-detail="fine" type="button">精细</button>
<button class="active" data-model-detail="ultra" type="button">超精细</button>
<button data-model-detail="solid" type="button">实体</button>
</div>
<div class="tool-section-title compact-title">
<span>STL</span>
<div class="title-actions">
<button id="selectAllStlBtn" type="button">全选</button>
<button id="invertStlBtn" type="button">反选</button>
</div>
<em id="stlCount">0</em>
</div>
<div id="modelRail" class="chip-rail"></div>
<div id="stlList" class="stl-list compact-list"></div>
</section>
<section class="tool-pane" data-tool-pane="pose">
<div class="tool-section-title">
<span>位姿手动调整</span>
<em id="saveState">未保存</em>
</div>
<div class="pose-actions">
<button id="resetRotationBtn" type="button">重置旋转</button>
<button id="resetTransformBtn" type="button">重置平移缩放</button>
<button id="resetFlipBtn" type="button">重置镜像</button>
</div>
<div class="flip-row">
<button data-flip="flipX">镜像 X</button>
<button data-flip="flipY">镜像 Y</button>
<button data-flip="flipZ">镜像 Z</button>
</div>
<div id="poseGrid" class="pose-grid"></div>
<div class="tool-section-title">
<span>位姿自动调整</span>
<em id="autoState">未运行</em>
</div>
<div class="auto-box compact-auto">
<button id="openAutoModalBtn" class="wide-action" type="button">自动调整设置</button>
<div id="autoSettingsPanel" class="auto-settings-panel">
<div class="auto-options modal-options">
<label><input id="autoX" type="checkbox" checked /> X 方向</label>
<label><input id="autoY" type="checkbox" checked /> Y 方向</label>
<label><input id="autoZ" type="checkbox" checked /> Z 方向</label>
<label><input id="autoScale" type="checkbox" checked /> 缩放</label>
</div>
<div class="auto-config-grid">
<section>
<div class="modal-section-head">
<strong>STL 参考区域</strong>
<button id="suggestBoneBtn" type="button">建议选择</button>
</div>
<div id="autoBoneOptions" class="bone-option-list"></div>
</section>
<section>
<div class="modal-section-head">
<strong>采样切片</strong>
<button id="autoDefaultsBtn" type="button">默认数量</button>
</div>
<div class="modal-slider">
<input id="autoSampleSlices" type="range" min="3" max="60" value="9" />
<input id="autoSampleSlicesNumber" type="number" min="3" max="60" value="9" />
</div>
</section>
</div>
<div class="auto-weight-grid">
<label>命中奖励<input id="autoBoneReward" type="number" step="0.1" value="1" /></label>
<label>区域惩罚<input id="autoOutsidePenalty" type="number" step="0.05" value="0.1" /></label>
<label>移动惩罚<input id="autoMovePenalty" type="number" step="0.01" value="0" /></label>
<label>缩放惩罚<input id="autoScalePenalty" type="number" step="0.01" value="0" /></label>
<label>迭代轮次<input id="autoIterations" type="number" min="1" max="100" value="30" /></label>
<label>每轮候选<input id="autoCandidates" type="number" min="6" max="96" value="36" /></label>
</div>
<div class="registration-operation-hint">
下方为配准操作区:保存当前位姿、保存并迭代自动微调,或退回上次自动调整前位姿。
</div>
<div class="pose-actions modal-actions save-pose-actions">
<button id="savePoseBtn" type="button">保存位姿</button>
<button id="saveIterateBtn" type="button">保存并迭代</button>
<button id="restorePoseBtn" type="button">退回位姿</button>
</div>
<div id="autoPoseResult" class="auto-pose-result">
<span>旋转 X 0</span>
<span>旋转 Y 0</span>
<span>旋转 Z 0</span>
<span>平移 X 0</span>
<span>平移 Y 0</span>
<span>平移 Z 0</span>
<span>缩放 1</span>
</div>
</div>
<div id="autoResult" class="auto-result">选择 DICOM 序列和 STL 后可运行。</div>
<div id="autoProgress" class="auto-progress hidden">
<div class="auto-progress-head">
<b id="autoProgressTitle">准备迭代</b>
<span id="autoProgressText">0%</span>
</div>
<div class="auto-progress-track"><i id="autoProgressFill"></i></div>
<div id="autoProgressSteps" class="auto-progress-steps"></div>
<p id="autoProgressDetail">等待自动微调任务。</p>
</div>
</div>
<textarea id="notes" class="notes" rows="2" placeholder="配准备注"></textarea>
</section>
</aside>
<section class="viewer-panel fusion-panel panel">
<div class="viewer-head">
<div class="fusion-head-stack">
<div class="fusion-action-row">
<div class="segmented">
<button data-fusion-mode="fusion" class="active">融合结果</button>
<button data-fusion-mode="model">单独模型</button>
</div>
<div class="fusion-save-actions">
<button id="saveBtn" class="primary-btn">保存配准</button>
<button id="statusBtn" class="state-btn">标为已配准</button>
</div>
</div>
<div class="fusion-control-row">
<div class="segmented">
<button data-window="default" class="active">默认</button>
<button data-window="bone">骨窗</button>
<button data-window="soft">软组织</button>
<button data-window="contrast">高对比</button>
</div>
<div class="viewer-tools">
<button id="stretchXBtn" class="ghost-btn">X拉伸</button>
<button id="stretchYBtn" class="ghost-btn">Y拉伸</button>
<button id="stretchZBtn" class="ghost-btn">Z拉伸</button>
</div>
</div>
</div>
</div>
<div class="fusion-stage">
<div id="fusionViewport" aria-label="DICOM 与 STL 三维融合视图"></div>
<div id="fusionWebglError" class="fusion-webgl-error hidden">
<div>
<strong>三维融合视图无法启动</strong>
<p>请检查浏览器硬件加速、显卡驱动或远程桌面图形支持。二维 DICOM 与映射功能仍可继续使用。</p>
</div>
</div>
<div class="fusion-hud fusion-status" id="fusionStatus">等待选择 DICOM 与 STL</div>
<div class="fusion-hud fusion-meta" id="fusionMeta">DICOM - · STL -</div>
<button id="fitBtn" class="fusion-reset-btn" type="button" title="重置影像与模型融合视角位置">
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M3 12a9 9 0 0 1 15.2-6.5L21 8.3M21 4v4.3h-4.3M21 12a9 9 0 0 1-15.2 6.5L3 15.7M3 20v-4.3h4.3" />
</svg>
位置重置
</button>
<div class="fusion-axis-inset" title="当前视角下模型平移 XYZ 方向" aria-hidden="true">
<svg width="54" height="54" viewBox="0 0 54 54">
<defs>
<marker id="fusion-axis-arrow-x" markerWidth="5" markerHeight="5" refX="4.3" refY="2.5" orient="auto" markerUnits="strokeWidth">
<path d="M0,0 L5,2.5 L0,5 Z" fill="#ef4444" />
</marker>
<marker id="fusion-axis-arrow-y" markerWidth="5" markerHeight="5" refX="4.3" refY="2.5" orient="auto" markerUnits="strokeWidth">
<path d="M0,0 L5,2.5 L0,5 Z" fill="#22c55e" />
</marker>
<marker id="fusion-axis-arrow-z" markerWidth="5" markerHeight="5" refX="4.3" refY="2.5" orient="auto" markerUnits="strokeWidth">
<path d="M0,0 L5,2.5 L0,5 Z" fill="#38bdf8" />
</marker>
</defs>
<circle cx="25" cy="31" r="2.2" />
<g id="fusionAxisX">
<line id="fusionAxisXLine" x1="25" y1="31" x2="42" y2="31" class="axis-x" marker-end="url(#fusion-axis-arrow-x)" />
<text id="fusionAxisXText" x="46" y="28" class="axis-x-text" text-anchor="start">X</text>
</g>
<g id="fusionAxisY">
<line id="fusionAxisYLine" x1="25" y1="31" x2="15" y2="41" class="axis-y" marker-end="url(#fusion-axis-arrow-y)" />
<text id="fusionAxisYText" x="11" y="47" class="axis-y-text" text-anchor="end">Y</text>
</g>
<g id="fusionAxisZ">
<line id="fusionAxisZLine" x1="25" y1="31" x2="25" y2="14" class="axis-z" marker-end="url(#fusion-axis-arrow-z)" />
<text id="fusionAxisZText" x="29" y="11" class="axis-z-text" text-anchor="start">Z</text>
</g>
</svg>
</div>
<div id="fusionLoading" class="fusion-loading hidden">
<span>正在融合三维影像与模型</span>
<div><i id="fusionProgressFill"></i></div>
<em id="fusionProgressText">0%</em>
</div>
</div>
<div class="slice-range-panel">
<div class="slice-range-head">
<strong>DICOM 切片范围</strong>
<span id="sliceRangeLabel">0 - 0 / 0</span>
</div>
<div class="dual-range">
<input id="sliceRangeStart" type="range" min="0" max="0" value="0" />
<input id="sliceRangeEnd" type="range" min="0" max="0" value="0" />
</div>
<div class="slice-range-foot">
<span id="sliceStartLabel">起点 0</span>
<span>范围</span>
<span id="sliceEndLabel">终点 0</span>
</div>
</div>
</section>
<section class="viewer-panel dicom-panel panel">
<div class="viewer-head">
<div class="mapping-title-row">
<div class="segmented">
<button data-map-mode="result" class="active">分割结果</button>
<button data-map-mode="image">单独影像</button>
</div>
</div>
<div class="mapping-toolbar">
<div class="segmented mini-segmented">
<button data-map-window="default" class="active">默认</button>
<button data-map-window="bone">骨窗</button>
<button data-map-window="soft">软组织</button>
<button data-map-window="contrast">高对比</button>
</div>
<div class="viewer-tools">
<button id="mappingRotateLeftBtn" class="ghost-btn" type="button">左旋</button>
<button id="mappingRotateRightBtn" class="ghost-btn" type="button">右旋</button>
<button id="mappingResetBtn" class="ghost-btn" type="button">位置重置</button>
</div>
</div>
</div>
<div class="dicom-stage">
<div id="mappingViewport" class="mapping-viewport">
<div id="mappingLayer" class="mapping-layer">
<img id="dicomPreview" alt="DICOM 切片" />
<canvas id="mappingCanvas"></canvas>
</div>
</div>
<div class="mapping-slice-rail">
<input id="mappingSliceSlider" type="range" min="0" max="0" value="0" />
<span id="mappingSliceLabel">0 / 0</span>
</div>
<div class="dicom-overlay top-left" id="dicomInfoLeft">未选择序列</div>
<div class="dicom-overlay top-right" id="dicomInfoRight"></div>
<div class="dicom-overlay bottom-left" id="dicomInfoBottom">Img: -</div>
<div id="dicomLoading" class="fusion-loading hidden">
<span>正在加载分割结果</span>
<div><i id="dicomProgressFill"></i></div>
<em id="dicomProgressText">0%</em>
</div>
</div>
<div class="mapping-summary">
<div class="mapping-summary-head">
<span id="mappingSummaryTitle">当前分割构件</span>
<em id="mappingStats">0/0 构件 · 0 边 · 0 px</em>
</div>
<div id="mappingLegend" class="mapping-legend"></div>
</div>
</section>
</section>
</section>
</main>
<script type="module" src="/static/app.js"></script>
</body>
</html>