2026-05-20-15-33-38 逆向映射导航外置与遮挡优化
This commit is contained in:
@@ -6,8 +6,8 @@ import {
|
||||
RotateCw,
|
||||
Rotate3d,
|
||||
AlertCircle,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
Eye,
|
||||
Layers,
|
||||
Save,
|
||||
@@ -1882,8 +1882,8 @@ export function VoxelizationMappingView({
|
||||
|
||||
return (
|
||||
<div className="relative flex h-full min-h-[420px] flex-col overflow-hidden rounded-3xl border border-slate-900 bg-slate-950 shadow-2xl">
|
||||
<div className="relative min-h-0 flex-1 overflow-hidden bg-black">
|
||||
<div className="absolute left-4 top-4 z-10 flex flex-wrap gap-2">
|
||||
<div className="flex items-center justify-between gap-3 border-b border-white/10 bg-slate-950 px-4 py-3">
|
||||
<div className="flex min-w-0 flex-wrap gap-2">
|
||||
<span className="rounded-lg border border-white/10 bg-black/65 px-2.5 py-1 text-[9px] font-bold uppercase tracking-widest text-slate-200">
|
||||
Base DICOM
|
||||
</span>
|
||||
@@ -1891,72 +1891,78 @@ export function VoxelizationMappingView({
|
||||
Overlay Label Map
|
||||
</span>
|
||||
</div>
|
||||
<div className="absolute right-4 top-4 z-10 rounded-lg border border-white/10 bg-black/65 px-2.5 py-1 text-[10px] font-mono text-white/70">
|
||||
<div className="shrink-0 rounded-lg border border-white/10 bg-black/65 px-2.5 py-1 text-[10px] font-mono text-white/70">
|
||||
Z {safeSlice + 1}/{Math.max(totalSlices, 1)}
|
||||
</div>
|
||||
{dicomPreview ? (
|
||||
<div
|
||||
className="absolute inset-0 flex items-center justify-center"
|
||||
style={{ transform: `rotate(${rotation}deg)` }}
|
||||
>
|
||||
<canvas ref={baseCanvasRef} className="absolute inset-0 h-full w-full object-contain" />
|
||||
<canvas ref={overlayCanvasRef} className="absolute inset-0 h-full w-full object-contain" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="absolute inset-0 flex items-center justify-center px-8 text-center text-xs font-bold text-white/40">
|
||||
{dicomStatus}
|
||||
</div>
|
||||
)}
|
||||
<div className="pointer-events-none absolute inset-x-4 bottom-4 z-10 rounded-2xl border border-white/10 bg-black/70 p-3 backdrop-blur-sm">
|
||||
<div className="mb-2 flex items-center justify-between gap-3 text-[10px] font-bold text-white/70">
|
||||
<span className="truncate">{overlayStatus}</span>
|
||||
<span className="font-mono text-cyan-100">
|
||||
{overlayStats.activeModules}/{visibleModuleCount} 构件 · {overlayStats.segmentCount} 边 · {overlayStats.filledPixels} px
|
||||
</span>
|
||||
</div>
|
||||
<div className="max-h-20 overflow-auto pr-1">
|
||||
{overlayStats.modules.length ? (
|
||||
<div className="grid grid-cols-2 gap-1.5 xl:grid-cols-3">
|
||||
{overlayStats.modules.map((item) => (
|
||||
<div key={item.fileName} className="grid grid-cols-[10px_1fr_auto] items-center gap-1 rounded-md border border-white/10 bg-white/5 px-1.5 py-1 text-[8px] font-bold text-white/70">
|
||||
<span className="h-2 w-2 rounded-sm border border-white/20" style={{ backgroundColor: item.color, opacity: item.opacity }} />
|
||||
<span className="min-w-0 truncate">{item.name}</span>
|
||||
<span className="font-mono text-cyan-100">ID {item.partId}</span>
|
||||
<span className="col-start-2 font-mono text-white/35">{item.segmentCount} 边</span>
|
||||
<span className="font-mono text-white/35">{item.filledPixels} px</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="rounded-lg border border-white/10 bg-white/5 px-2 py-1.5 text-[9px] font-bold text-white/35">
|
||||
当前切片暂无可见构件
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-white/10 bg-slate-950 px-4 py-3">
|
||||
<div className="mb-2 flex items-center justify-between">
|
||||
<p className="text-[10px] font-bold uppercase tracking-widest text-slate-400">Slice Navigator</p>
|
||||
<span className="font-mono text-[10px] font-bold text-cyan-100">
|
||||
{safeSlice + 1} / {Math.max(totalSlices, 1)}
|
||||
</span>
|
||||
<div className="grid min-h-0 flex-1 grid-cols-[minmax(0,1fr)_76px]">
|
||||
<div className="flex min-h-0 flex-col">
|
||||
<div className="relative min-h-0 flex-1 overflow-hidden bg-black">
|
||||
{dicomPreview ? (
|
||||
<div
|
||||
className="absolute inset-0 flex items-center justify-center"
|
||||
style={{ transform: `rotate(${rotation}deg)` }}
|
||||
>
|
||||
<canvas ref={baseCanvasRef} className="absolute inset-0 h-full w-full object-contain" />
|
||||
<canvas ref={overlayCanvasRef} className="absolute inset-0 h-full w-full object-contain" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="absolute inset-0 flex items-center justify-center px-8 text-center text-xs font-bold text-white/40">
|
||||
{dicomStatus}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="border-t border-white/10 bg-slate-950 px-4 py-3">
|
||||
<div className="mb-2 flex items-center justify-between gap-3 text-[10px] font-bold text-white/70">
|
||||
<span className="truncate">{overlayStatus}</span>
|
||||
<span className="font-mono text-cyan-100">
|
||||
{overlayStats.activeModules}/{visibleModuleCount} 构件 · {overlayStats.segmentCount} 边 · {overlayStats.filledPixels} px
|
||||
</span>
|
||||
</div>
|
||||
<div className="max-h-24 overflow-auto pr-1">
|
||||
{overlayStats.modules.length ? (
|
||||
<div className="grid grid-cols-2 gap-1.5 xl:grid-cols-3">
|
||||
{overlayStats.modules.map((item) => (
|
||||
<div key={item.fileName} className="grid grid-cols-[10px_1fr_auto] items-center gap-1 rounded-md border border-white/10 bg-white/5 px-1.5 py-1 text-[8px] font-bold text-white/70">
|
||||
<span className="h-2 w-2 rounded-sm border border-white/20" style={{ backgroundColor: item.color, opacity: item.opacity }} />
|
||||
<span className="min-w-0 truncate">{item.name}</span>
|
||||
<span className="font-mono text-cyan-100">ID {item.partId}</span>
|
||||
<span className="col-start-2 font-mono text-white/35">{item.segmentCount} 边</span>
|
||||
<span className="font-mono text-white/35">{item.filledPixels} px</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="rounded-lg border border-white/10 bg-white/5 px-2 py-1.5 text-[9px] font-bold text-white/35">
|
||||
当前切片暂无可见构件
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-[28px_1fr_28px] items-center gap-3">
|
||||
|
||||
<aside className="flex min-h-0 flex-col items-center gap-3 border-l border-white/10 bg-slate-900/95 px-3 py-4">
|
||||
<div className="w-full rounded-2xl border border-white/10 bg-slate-950/90 px-2 py-3 text-center">
|
||||
<p className="text-[10px] font-bold text-slate-300">DICOM 切片位置</p>
|
||||
<span className="mt-1 block font-mono text-[10px] font-bold text-cyan-100">
|
||||
{safeSlice + 1} / {Math.max(totalSlices, 1)}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => stepSlice(-1)}
|
||||
disabled={safeSlice <= 0}
|
||||
className="flex h-7 w-7 items-center justify-center rounded-lg border border-slate-700 bg-slate-900 text-slate-200 hover:border-cyan-400 hover:text-cyan-100 disabled:opacity-35"
|
||||
className="flex h-8 w-8 items-center justify-center rounded-xl border border-slate-700 bg-slate-950 text-slate-200 hover:border-cyan-400 hover:text-cyan-100 disabled:opacity-35"
|
||||
title="上一层"
|
||||
>
|
||||
<ChevronLeft size={15} />
|
||||
<ChevronUp size={16} />
|
||||
</button>
|
||||
<div className="relative h-8">
|
||||
<div className="absolute inset-x-0 top-1/2 h-2 -translate-y-1/2 rounded-full bg-slate-800" />
|
||||
<div className="relative min-h-[240px] w-10 flex-1">
|
||||
<div className="absolute inset-y-0 left-1/2 w-2 -translate-x-1/2 rounded-full bg-slate-800" />
|
||||
<div
|
||||
className="absolute top-1/2 h-2 -translate-y-1/2 rounded-full bg-cyan-400"
|
||||
style={{ left: 0, width: `${slicePercent}%` }}
|
||||
className="absolute bottom-0 left-1/2 w-2 -translate-x-1/2 rounded-full bg-cyan-400"
|
||||
style={{ height: `${slicePercent}%` }}
|
||||
/>
|
||||
<input
|
||||
type="range"
|
||||
@@ -1964,19 +1970,24 @@ export function VoxelizationMappingView({
|
||||
max={maxSlice}
|
||||
value={safeSlice}
|
||||
onChange={(event) => onSliceChange(Number(event.target.value))}
|
||||
className="mapping-slice-input"
|
||||
className="mapping-slice-vertical-input"
|
||||
aria-label="逆向分割映射视图切片导航"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => stepSlice(1)}
|
||||
disabled={safeSlice >= maxSlice}
|
||||
className="flex h-7 w-7 items-center justify-center rounded-lg border border-slate-700 bg-slate-900 text-slate-200 hover:border-cyan-400 hover:text-cyan-100 disabled:opacity-35"
|
||||
className="flex h-8 w-8 items-center justify-center rounded-xl border border-slate-700 bg-slate-950 text-slate-200 hover:border-cyan-400 hover:text-cyan-100 disabled:opacity-35"
|
||||
title="下一层"
|
||||
>
|
||||
<ChevronRight size={15} />
|
||||
<ChevronDown size={16} />
|
||||
</button>
|
||||
</div>
|
||||
<div className="grid w-full grid-cols-1 gap-1 text-center text-[9px] font-bold text-slate-500">
|
||||
<span>顶层 {Math.max(totalSlices, 1)}</span>
|
||||
<span className="text-cyan-200">当前 {safeSlice + 1}</span>
|
||||
<span>底层 1</span>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user