实现在 Cornerstone3D 医学图像查看器中显示 DICOM 图像四角标签信息的功能。该功能支持:
MorePanel (src/pages/view/components/MorePanel.tsx)
StackViewer (src/pages/view/components/viewers/stack.image.viewer.tsx)
DicomOverlayTool (src/components/overlay/DicomOverlayTool.ts)
AnnotationTool (Cornerstone3D)IMAGE_RENDERED, STACK_NEW_IMAGE 事件OverlayRenderer (src/components/overlay/renderers/OverlayRenderer.ts)
IOverlayConfigProvider (接口)
getOverlayConfig(), isAvailable()LocalOverlayConfigAdapter
RemoteOverlayConfigAdapter
/api/config/overlayOverlayConfigManager
DicomTagFormatter (src/utils/dicomTagFormatter.ts)
OverlayPositionCalculator (src/utils/overlayPositionCalculator.ts)
src/states/view/dicomOverlaySlice.ts)
enabled, configSource, configtoggleOverlay, setConfigSource, updateConfigselectOverlayEnabled, selectOverlayConfig// 单个标签配置
interface TagConfig {
tag: string; // DICOM tag (如 "0010,0010")
label?: string; // 显示标签 (如 "患者姓名")
format?: TagFormat; // 格式化选项
defaultValue?: string; // 默认值
}
// 位置配置
type CornerPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
// 角落配置
interface CornerConfig {
position: CornerPosition;
tags: TagConfig[];
visible: boolean;
}
// 完整配置
interface OverlayConfig {
enabled: boolean;
corners: CornerConfig[];
style: OverlayStyle; // 字体、颜色等
}
[ ] 创建类型定义 src/config/overlayConfig/types/overlayConfig.ts
TagConfig 接口CornerConfig 接口OverlayConfig 接口TagFormat 枚举OverlayStyle 接口[ ] 创建配置接口 src/config/overlayConfig/ports/IOverlayConfigProvider.ts
getOverlayConfig() 方法签名isAvailable() 方法签名[ ] 实现本地适配器 src/config/overlayConfig/adapters/LocalOverlayConfigAdapter.ts
IOverlayConfigProvider 接口[ ] 实现远程适配器 src/config/overlayConfig/adapters/RemoteOverlayConfigAdapter.ts
IOverlayConfigProvider 接口[ ] 创建配置管理器 src/config/overlayConfig/OverlayConfigManager.ts
[ ] 创建格式化工具 src/utils/dicomTagFormatter.ts
[ ] 创建位置计算工具 src/utils/overlayPositionCalculator.ts
[ ] 创建渲染器 src/components/overlay/renderers/OverlayRenderer.ts
[ ] 创建 DicomOverlayTool src/components/overlay/DicomOverlayTool.ts
AnnotationTool 基类IMAGE_RENDERED 事件STACK_NEW_IMAGE 事件[ ] 创建文档 src/components/overlay/DicomOverlayTool.README.md
[ ] 创建 Redux Slice src/states/view/dicomOverlaySlice.ts
toggleOverlay actionsetConfigSource actionupdateConfig action[ ] 集成到 Store src/states/store.ts
[ ] 修改 MorePanel src/pages/view/components/MorePanel.tsx
handleTagInfo 函数[ ] 修改 StackViewer src/pages/view/components/viewers/stack.image.viewer.tsx
registerTools 中注册 DicomOverlayTooluseEffect 监听 overlay 状态src/utils/cornerstoneToolsSetup.ts
DicomOverlayTool[ ] 编写完整文档 docs/实现/四角DICOM信息显示功能.md
[ ] 制定测试方案
sequenceDiagram
actor User as 用户
participant MP as MorePanel
participant Redux as Redux Store
participant SV as StackViewer
participant Tool as DicomOverlayTool
participant Config as ConfigManager
participant Renderer as OverlayRenderer
participant CS as Cornerstone3D
User->>MP: 点击"tag信息"按钮
MP->>Redux: dispatch(toggleOverlay())
Redux->>Redux: 更新 enabled 状态
Redux-->>SV: 状态变化通知
SV->>SV: useEffect 检测状态变化
alt overlay enabled
SV->>Tool: 激活工具
Tool->>Config: 获取配置
Config-->>Tool: 返回配置
Tool->>CS: 监听 IMAGE_RENDERED 事件
CS->>Tool: IMAGE_RENDERED 事件
Tool->>Tool: 提取 DICOM metadata
Tool->>Tool: 格式化标签值
Tool->>Renderer: 渲染四角信息
Renderer->>CS: 绘制到 Canvas
else overlay disabled
SV->>Tool: 停用工具
Tool->>CS: 移除监听
Tool->>Renderer: 清除渲染
end
User->>User: 切换图像 (Stack Scroll)
CS->>Tool: STACK_NEW_IMAGE 事件
Tool->>Tool: 提取新图像 metadata
Tool->>Renderer: 更新渲染
Renderer->>CS: 重新绘制
flowchart TD
A[用户点击按钮] --> B{Redux State}
B -->|enabled=true| C[StackViewer 检测]
B -->|enabled=false| D[禁用工具]
C --> E[激活 DicomOverlayTool]
E --> F[ConfigManager]
F --> G{配置源}
G -->|Remote| H[RemoteAdapter]
G -->|Local| I[LocalAdapter]
H -->|成功| J[配置数据]
H -->|失败| I
I --> J
J --> K[DicomOverlayTool]
K --> L[监听事件]
L --> M{事件类型}
M -->|IMAGE_RENDERED| N[提取 Metadata]
M -->|STACK_NEW_IMAGE| N
M -->|VOI_MODIFIED| N
N --> O[DicomTagFormatter]
O --> P[格式化值]
P --> Q[OverlayPositionCalculator]
Q --> R[计算位置]
R --> S[OverlayRenderer]
S --> T[Canvas 渲染]
D --> U[清除显示]
/**
* DICOM 标签格式化选项
*/
export enum TagFormat {
/** 原始值 */
RAW = 'raw',
/** 日期格式 YYYY-MM-DD */
DATE = 'date',
/** 时间格式 HH:MM:SS */
TIME = 'time',
/** 日期时间格式 */
DATETIME = 'datetime',
/** 数字,保留2位小数 */
NUMBER_2 = 'number_2',
/** 数字,保留1位小数 */
NUMBER_1 = 'number_1',
/** 整数 */
INTEGER = 'integer',
/** 性别枚举 (M/F/O) */
SEX = 'sex',
/** 自定义函数 */
CUSTOM = 'custom',
}
/**
* 单个 DICOM 标签配置
*/
export interface TagConfig {
/** DICOM 标签 (如 "0010,0010" 或 "PatientName") */
tag: string;
/** 显示标签文本 */
label?: string;
/** 格式化选项 */
format?: TagFormat;
/** 自定义格式化函数 */
customFormatter?: (value: any) => string;
/** 默认值(标签不存在时) */
defaultValue?: string;
/** 是否显示标签名 */
showLabel?: boolean;
}
/**
* 角落位置
*/
export type CornerPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
/**
* 单个角落的配置
*/
export interface CornerConfig {
/** 位置 */
position: CornerPosition;
/** 该角落显示的标签列表 */
tags: TagConfig[];
/** 是否可见 */
visible: boolean;
/** 对齐方式 */
align?: 'left' | 'right';
}
/**
* 样式配置
*/
export interface OverlayStyle {
/** 字体系列 */
fontFamily?: string;
/** 字体大小 */
fontSize?: number;
/** 字体颜色 */
color?: string;
/** 背景颜色(支持透明度) */
backgroundColor?: string;
/** 内边距 */
padding?: number;
/** 行高 */
lineHeight?: number;
/** 是否显示背景 */
showBackground?: boolean;
}
/**
* 完整的 Overlay 配置
*/
export interface OverlayConfig {
/** 全局开关 */
enabled: boolean;
/** 四个角的配置 */
corners: CornerConfig[];
/** 样式配置 */
style: OverlayStyle;
/** 配置版本 */
version?: string;
/** 更新时间 */
updatedAt?: string;
}
/**
* 配置响应
*/
export interface OverlayConfigResponse {
data: OverlayConfig;
success: boolean;
message?: string;
}
/**
* Overlay Redux State
*/
export interface DicomOverlayState {
/** 是否启用 */
enabled: boolean;
/** 配置源 */
configSource: 'local' | 'remote';
/** 当前配置 */
config: OverlayConfig | null;
/** 加载状态 */
loading: boolean;
/** 错误信息 */
error: string | null;
}
/**
* 渲染项
*/
export interface OverlayRenderItem {
/** 显示文本 */
text: string;
/** 位置 */
position: { x: number; y: number };
/** 角落 */
corner: CornerPosition;
/** 样式 */
style: OverlayStyle;
}
用户操作: 用户点击 MorePanel 中的 "tag信息" 按钮
flowchart TD
Start([用户点击 tag信息 按钮]) --> A[MorePanel.handleTagInfo]
A --> B[dispatch toggleOverlay action]
B --> C[Redux 更新 enabled 状态]
C --> D{enabled?}
D -->|true 启用| E[StackViewer useEffect 触发]
E --> F[获取 toolGroup]
F --> G[检查 DicomOverlayTool 是否已注册]
G -->|未注册| H[注册工具到 toolGroup]
G -->|已注册| I[激活工具]
H --> I
I --> J[ConfigManager.getConfig]
J --> K{配置源}
K -->|Remote| L[RemoteAdapter.fetch]
L -->|成功| M[返回配置]
L -->|失败| N[LocalAdapter.getConfig]
K -->|Local| N
N --> M
M --> O[DicomOverlayTool.setConfig]
O --> P[添加事件监听器]
P --> P1[IMAGE_RENDERED]
P --> P2[STACK_NEW_IMAGE]
P --> P3[VOI_MODIFIED]
P1 --> Q[onImageRendered]
P2 --> Q
P3 --> Q
Q --> R[获取当前 imageId]
R --> S[从 metaDataManager 提取 metadata]
S --> T[遍历配置的 tags]
T --> U[DicomTagFormatter.format]
U --> V[OverlayPositionCalculator.calculate]
V --> W[准备渲染数据]
W --> X[OverlayRenderer.render]
X --> Y[Canvas 绘制文本]
Y --> End1([四角信息显示])
D -->|false 禁用| Z[StackViewer useEffect 触发]
Z --> AA[停用工具]
AA --> AB[移除事件监听器]
AB --> AC[清除 Canvas]
AC --> End2([四角信息隐藏])
初始化阶段
cornerstoneToolsSetup.ts 中全局注册 DicomOverlayToolStackViewer 的 registerTools 函数中添加工具到 toolGroup激活阶段
StackViewer 通过 useSelector 监听状态useEffect 响应状态变化,调用 toolGroup.setToolActive/Passive配置加载
事件监听
IMAGE_RENDERED: 图像渲染完成时更新STACK_NEW_IMAGE: 切换图像时更新VOI_MODIFIED: 窗宽窗位变化时更新(如需显示WW/WL)数据提取与格式化
cornerstoneDICOMImageLoader.wadors.metaDataManager 获取 metadata渲染
前置条件:
测试步骤:
前置条件:
测试步骤:
前置条件:
测试步骤:
前置条件:
测试步骤:
前置条件:
测试步骤:
测试步骤:
测试步骤:
测试步骤:
测试步骤:
测试步骤:
graph TB
subgraph UI["UI 层"]
MP[MorePanel<br/>tag信息按钮]
SV[StackViewer<br/>图像显示]
end
subgraph State["状态管理层"]
Redux[Redux Store<br/>dicomOverlaySlice]
end
subgraph Core["核心层"]
Tool[DicomOverlayTool<br/>核心工具]
Renderer[OverlayRenderer<br/>渲染器]
end
subgraph Config["配置层"]
Manager[ConfigManager<br/>配置管理器]
LocalAdapter[LocalAdapter<br/>本地配置]
RemoteAdapter[RemoteAdapter<br/>远程配置]
end
subgraph Utils["工具层"]
Formatter[DicomTagFormatter<br/>格式化]
Calculator[PositionCalculator<br/>位置计算]
end
subgraph External["外部依赖"]
CS[Cornerstone3D<br/>渲染引擎]
MetaData[MetaDataManager<br/>元数据]
end
MP -->|dispatch action| Redux
Redux -->|state change| SV
SV -->|activate/deactivate| Tool
Tool -->|get config| Manager
Manager -->|try remote| RemoteAdapter
Manager -->|fallback| LocalAdapter
Tool -->|extract metadata| MetaData
Tool -->|format values| Formatter
Tool -->|calculate position| Calculator
Tool -->|render| Renderer
Renderer -->|draw| CS
Tool -->|listen events| CS
style MP fill:#e1f5ff
style Redux fill:#fff4e1
style Tool fill:#ffe1e1
style Manager fill:#e1ffe1
classDiagram
class IOverlayConfigProvider {
<<interface>>
+getOverlayConfig() Promise~OverlayConfig~
+isAvailable() Promise~boolean~
}
class LocalOverlayConfigAdapter {
-defaultConfig: OverlayConfig
+getOverlayConfig() Promise~OverlayConfig~
+isAvailable() Promise~boolean~
}
class RemoteOverlayConfigAdapter {
-apiUrl: string
-cache: OverlayConfig
+getOverlayConfig() Promise~OverlayConfig~
+isAvailable() Promise~boolean~
-fetchFromServer() Promise~OverlayConfig~
}
class OverlayConfigManager {
-providers: IOverlayConfigProvider[]
-currentConfig: OverlayConfig
+getConfig() Promise~OverlayConfig~
+setConfigSource(source: string) void
-selectProvider() IOverlayConfigProvider
}
class DicomOverlayTool {
-config: OverlayConfig
-renderer: OverlayRenderer
-eventHandlers: Map
+onSetToolEnabled() void
+onSetToolDisabled() void
+renderAnnotation() void
-onImageRendered() void
-extractMetadata() object
}
class OverlayRenderer {
-canvas: HTMLCanvasElement
-ctx: CanvasRenderingContext2D
+render(items: OverlayRenderItem[]) void
+clear() void
-drawText(text: string, position: Point) void
-drawBackground(rect: Rect) void
}
class DicomTagFormatter {
+formatDate(value: string) string
+formatTime(value: string) string
+formatNumber(value: number, decimals: number) string
+formatSex(value: string) string
+format(value: any, format: TagFormat) string
}
class OverlayPositionCalculator {
+calculate(corner: CornerPosition, viewport: Viewport) Point
-getCornerOffset(corner: CornerPosition) Point
-calculateMultilineHeight(lines: number) number
}
IOverlayConfigProvider <|.. LocalOverlayConfigAdapter
IOverlayConfigProvider <|.. RemoteOverlayConfigAdapter
OverlayConfigManager o-- IOverlayConfigProvider
DicomOverlayTool --> OverlayConfigManager
DicomOverlayTool --> OverlayRenderer
DicomOverlayTool --> DicomTagFormatter
DicomOverlayTool --> OverlayPositionCalculator
场景:
IMAGE_RENDERED 事件解决方案:
// 使用防抖优化
const debouncedRender = debounce(() => {
this.renderOverlay();
}, 16); // ~60fps
eventTarget.addEventListener(EVENTS.IMAGE_RENDERED, debouncedRender);
优化措施:
requestAnimationFrame 优化渲染时机场景:
解决方案:
// 使用 Web Worker 处理
const worker = new Worker('metadata-processor.worker.js');
worker.postMessage({ imageId, tags });
worker.onmessage = (e) => {
this.renderOverlay(e.data);
};
场景:
解决方案:
// 指定支持中文的字体
const style = {
fontFamily: 'Microsoft YaHei, SimHei, Arial, sans-serif',
// ...
};
// 检测字体是否加载
document.fonts.ready.then(() => {
this.renderOverlay();
});
场景:
解决方案:
// 添加浏览器检测和兼容代码
const measureText = (text: string, font: string) => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d')!;
ctx.font = font;
const metrics = ctx.measureText(text);
// 兼容不支持 fontBoundingBox 的浏览器
const height = metrics.fontBoundingBoxAscent
? metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent
: parseInt(font) * 1.2; // fallback
return { width: metrics.width, height };
};
场景:
解决方案:
const extractTagValue = (metadata: any, tag: string, defaultValue: string = '') => {
try {
// 支持多种 tag 格式: "0010,0010" 或 "PatientName"
const value = metadata[tag] || metadata[tag.replace(',', '')];
if (!value) {
console.warn(`Tag ${tag} not found in metadata`);
return defaultValue;
}
// 处理不同的值格式
if (typeof value === 'object' && value.Value) {
return value.Value[0] || defaultValue;
}
return String(value) || defaultValue;
} catch (error) {
console.error(`Error extracting tag ${tag}:`, error);
return defaultValue;
}
};
场景:
解决方案:
const formatDate = (value: string): string => {
// 处理各种可能的日期格式
const formats = [
/^(\d{4})(\d{2})(\d{2})$/, // YYYYMMDD
/^(\d{4})-(\d{2})-(\d{2})$/, // YYYY-MM-DD
/^(\d{4})\/(\d{2})\/(\d{2})$/, // YYYY/MM/DD
];
for (const format of formats) {
const match = value.match(format);
if (match) {
return `${match[1]}-${match[2]}-${match[3]}`;
}
}
return value; // 无法识别,返回原始值
};
场景:
解决方案:
// 添加透明度控制
const style = {
backgroundColor: 'rgba(0, 0, 0, 0.5)', // 半透明背景
// 或者在鼠标移入时隐藏
};
// 鼠标悬停时隐藏
canvas.addEventListener('mouseenter', () => {
this.setOpacity(0.2);
});
canvas.addEventListener('mouseleave', () => {
this.setOpacity(1.0);
});
场景:
解决方案:
const truncateText = (
text: string,
maxWidth: number,
ctx: CanvasRenderingContext2D
): string => {
const metrics = ctx.measureText(text);
if (metrics.width <= maxWidth) {
return text;
}
// 逐字符减少直到符合宽度
let truncated = text;
while (ctx.measureText(truncated + '...').width > maxWidth && truncated.length > 0) {
truncated = truncated.slice(0, -1);
}
return truncated + '...';
};
场景:
解决方案:
class OverlayConfigManager {
async getConfig(): Promise<OverlayConfig> {
try {
// 尝试远程配置
const remoteConfig = await this.remoteAdapter.getOverlayConfig();
// 验证配置有效性
if (this.validateConfig(remoteConfig)) {
return remoteConfig;
}
console.warn('Remote config invalid, falling back to local');
} catch (error) {
console.error('Failed to load remote config:', error);
}
// 降级到本地配置
return this.localAdapter.getOverlayConfig();
}
private validateConfig(config: OverlayConfig): boolean {
return (
config &&
Array.isArray(config.corners) &&
config.corners.every(corner =>
corner.position && Array.isArray(corner.tags)
)
);
}
}
场景:
解决方案:
class DicomOverlayTool {
private configVersion = 0;
async updateConfig(newConfig: OverlayConfig) {
const currentVersion = ++this.configVersion;
// 模拟异步操作
await this.processConfig(newConfig);
// 只有当前版本最新时才应用
if (currentVersion === this.configVersion) {
this.config = newConfig;
this.render();
} else {
console.log('Config outdated, skipping');
}
}
}
场景:
解决方案:
class DicomOverlayTool {
private eventHandlers = new Map<string, Function>();
onSetToolEnabled() {
const handler = this.onImageRendered.bind(this);
this.eventHandlers.set('IMAGE_RENDERED', handler);
eventTarget.addEventListener(EVENTS.IMAGE_RENDERED, handler);
}
onSetToolDisabled() {
// 清理所有事件监听器
this.eventHandlers.forEach((handler, event) => {
eventTarget.removeEventListener(event, handler);
});
this.eventHandlers.clear();
}
}
使用 TypeScript 严格模式
strict 选项any遵循项目代码风格
npm run lint:fix 格式化代码注释和文档
单元测试
集成测试
性能测试
本实现方案采用适配器模式和关注点分离的设计原则,提供了一个灵活、可扩展、易维护的四角 DICOM 信息显示功能。
sequenceDiagram
participant User as 👤 用户
participant MP as MorePanel
participant Redux as Redux Store
participant SV as StackViewer
participant TG as ToolGroup
participant Tool as DicomOverlayTool
participant CM as ConfigManager
participant CS as Cornerstone3D
User->>MP: 点击"tag信息"按钮
Note over MP: handleTagInfo()
MP->>Redux: dispatch(toggleOverlay())
Note over Redux: enabled: false → true
Redux-->>SV: 状态变化通知
Note over SV: useEffect监听overlayEnabled
SV->>TG: getToolGroup(viewportId)
TG-->>SV: 返回toolGroup实例
SV->>TG: setToolEnabled(DicomOverlayTool.toolName)
Note over TG: 工具状态: Passive → Enabled
TG->>Tool: onSetToolEnabled()
Note over Tool: 工具被激活
Tool->>CM: getConfig()
Note over CM: 尝试加载配置
alt 远程配置可用
CM->>CM: RemoteAdapter.fetch()
CM-->>Tool: 返回远程配置
else 远程配置失败
CM->>CM: LocalAdapter.getConfig()
CM-->>Tool: 返回本地配置
end
Note over Tool: 配置加载完成
Tool->>CS: 触发viewport.render()
CS->>Tool: renderAnnotation(enabledElement, svgHelper)
Note over Tool: Cornerstone自动调用渲染
Tool->>Tool: extractMetadata(imageId)
Tool->>Tool: prepareTextLines()
Tool->>Tool: renderToSVG()
Tool->>CS: 创建SVG文本元素
Note over CS: SVG layer
CS-->>User: 显示四角信息 ✅
sequenceDiagram
participant User as 👤 用户
participant MP as MorePanel
participant Redux as Redux Store
participant SV as StackViewer
participant TG as ToolGroup
participant Tool as DicomOverlayTool
participant CS as Cornerstone3D
User->>MP: 再次点击"tag信息"按钮
Note over MP: handleTagInfo()
MP->>Redux: dispatch(toggleOverlay())
Note over Redux: enabled: true → false
Redux-->>SV: 状态变化通知
Note over SV: useEffect监听overlayEnabled
SV->>TG: getToolGroup(viewportId)
TG-->>SV: 返回toolGroup实例
SV->>TG: setToolDisabled(DicomOverlayTool.toolName)
Note over TG: 工具状态: Enabled → Disabled
TG->>Tool: onSetToolDisabled()
Note over Tool: 工具被停用
Tool->>CS: 触发viewport.render()
Note over CS: 重新渲染viewport
CS->>Tool: renderAnnotation(enabledElement, svgHelper)
Note over Tool: 由于工具disabled,<br/>Cornerstone不再调用渲染
Note over CS: SVG元素被清除
CS-->>User: 四角信息消失 ✅
User (用户)
MorePanel (src/pages/view/components/MorePanel.tsx)
handleTagInfo(): 处理按钮点击src/states/view/dicomOverlaySlice.ts)
enabled: boolean: overlay开关状态toggleOverlay(): 切换enabled状态StackViewer (src/pages/view/components/viewers/stack.image.viewer.tsx)
useSelector(selectOverlayEnabled): 监听状态useEffect([overlayEnabled]): 响应状态变化关键逻辑:
useEffect(() => {
const toolGroup = ToolGroupManager.getToolGroup(`STACK_TOOL_GROUP_ID_${viewportId}`);
if (overlayEnabled) {
toolGroup.setToolEnabled(DicomOverlayTool.toolName);
} else {
toolGroup.setToolDisabled(DicomOverlayTool.toolName);
}
}, [overlayEnabled, viewportId]);
setToolEnabled(toolName): 启用工具setToolDisabled(toolName): 停用工具src/components/overlay/DicomOverlayTool.ts)onSetToolEnabled(): 工具启用回调
typescript
onSetToolEnabled(): void {
console.log('[DicomOverlayTool] Tool enabled');
this.loadConfig(); // 异步加载配置
}
onSetToolDisabled(): 工具停用回调
onSetToolDisabled(): void {
console.log('[DicomOverlayTool] Tool disabled');
// Cornerstone会自动停止调用renderAnnotation
}
renderAnnotation(enabledElement, svgHelper): 渲染回调
typescript
renderAnnotation(enabledElement, svgHelper): boolean {
if (!this.config) return false;
// 提取metadata
const metadata = this.extractMetadata(imageId);
// 渲染到SVG
this.renderer.renderToSVG(svgHelper, viewport, metadata, ...);
return true;
}
职责: 管理配置、提取数据、触发渲染
src/config/overlayConfig/OverlayConfigManager.ts)
getConfig(): 获取配置
typescript
async getConfig(): Promise<OverlayConfig> {
// 优先远程,失败降级本地
try {
return await this.remoteAdapter.getOverlayConfig();
} catch {
return await this.localAdapter.getOverlayConfig();
}
}
- 职责: 统一配置访问,处理降级
#### 🎨 渲染引擎层
8. Cornerstone3D
- 角色: 医学图像渲染引擎
- 关键职责:
- 在渲染循环中自动调用renderAnnotation()
- 管理SVG图层
- 响应工具状态变化
- 工作机制:
- 工具Enabled时: 渲染循环包含该工具
- 工具Disabled时: 渲染循环跳过该工具
### 13.4 关键流程说明
#### 启用时的关键步骤
1. 状态更新 (MorePanel → Redux)
- 用户点击触发toggleOverlay()
- Redux将enabled从false改为true
2. 状态监听 (Redux → StackViewer)
- useSelector检测到状态变化
- useEffect被触发
3. 工具激活 (StackViewer → ToolGroup)
- 调用toolGroup.setToolEnabled()
- ToolGroup触发工具的onSetToolEnabled()
4. 配置加载 (Tool → ConfigManager)
- 异步加载配置
- 加载完成后触发viewport.render()
5. 自动渲染 (Cornerstone → Tool)
- Cornerstone在渲染循环中调用renderAnnotation()
- 工具创建SVG元素显示信息
#### 停用时的关键步骤
1. 状态更新 (MorePanel → Redux)
- 用户再次点击触发toggleOverlay()
- Redux将enabled从true改为false
2. 状态监听 (Redux → StackViewer)
- useSelector检测到状态变化
- useEffect被触发
3. 工具停用 (StackViewer → ToolGroup)
- 调用toolGroup.setToolDisabled()
- ToolGroup触发工具的onSetToolDisabled()
4. 停止渲染 (Cornerstone自动处理)
- Cornerstone不再调用该工具的renderAnnotation()
- SVG元素被清除
- 四角信息消失
### 13.5 核心优势
✅ 自动化渲染: Cornerstone3D自动管理渲染循环,工具无需手动监听事件
✅ 声明式状态: 使用Redux管理状态,React自动响应变化
✅ 清晰的职责分离:
- UI层只负责触发action
- 工具层只负责渲染逻辑
- Cornerstone负责渲染调度
✅ SVG渲染: 不会被Canvas覆盖,永久显示
---
文档版本: 1.1.0