| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579 |
- // src/features/imageAnnotation/services/AnnotationManager.ts
- // 全局注释管理器 - 负责监听Cornerstone事件并管理注释的保存/加载
- import { eventTarget, getRenderingEngines } from '@cornerstonejs/core';
- import { EVENTS } from '@cornerstonejs/core';
- import { annotation as corAnnotation, Enums } from '@cornerstonejs/tools';
- import { AnnotationSerializer } from './AnnotationSerializer';
- import { AnnotationValidator } from './AnnotationValidator';
- import { annotationAPI } from '../../../API/annotation';
- import { extractSopInstanceUid } from '../../../utils/dicomUtils';
- import type { AnnotationData, AnnotationEventType } from '../types/annotation';
- import { IRenderingEngine, IStackViewport } from '@cornerstonejs/core/dist/esm/types';
- import * as cornerstoneTools from '@cornerstonejs/tools';
- import { getIpPort } from '../../../API/config';
- export class AnnotationManager {
- private eventListenersRegistered: boolean = false;
- private activeImageId: string | null = null;
- private serializer: AnnotationSerializer;
- private validator: AnnotationValidator;
- private pendingChanges: Map<string, string> = new Map();
- private saveTimeout: NodeJS.Timeout | null = null;
- private isLoading: boolean = false;
- constructor() {
- this.serializer = new AnnotationSerializer();
- this.validator = new AnnotationValidator();
- }
- async initialize(): Promise<void> {
- console.log('【annotationmanager】🔧 AnnotationManager 初始化中...');
- // 注册全局事件监听器
- this.registerGlobalEventListeners();
- console.log('【annotationmanager】🎧 全局注释事件监听器已设置');
- }
- async cleanup(): Promise<void> {
- console.log('【annotationmanager】🧹 AnnotationManager 清理中...');
- // 取消待处理的保存操作
- if (this.saveTimeout) {
- clearTimeout(this.saveTimeout);
- this.saveTimeout = null;
- }
- // 移除全局事件监听器
- this.unregisterGlobalEventListeners();
- console.log('【annotationmanager】✅ AnnotationManager 清理完成');
- }
- // ============ 事件监听器注册 ============
- private registerGlobalEventListeners(): void {
- if (this.eventListenersRegistered) {
- console.warn('【annotationmanager】事件监听器已注册,跳过重复注册');
- return;
- }
- console.log('【annotationmanager】注册事件监听器...');
- // 图像加载完成事件
- eventTarget.addEventListener(
- EVENTS.IMAGE_LOADED,
- this.handleImageLoadCompleted
- );
- // 图像渲染完成事件
- eventTarget.addEventListener(
- EVENTS.IMAGE_RENDERED,
- this.handleImageRendered
- );
- // 注释完成事件
- eventTarget.addEventListener(
- Enums.Events.ANNOTATION_COMPLETED,
- this.handleAnnotationCompleted
- );
- // 注释添加事件
- eventTarget.addEventListener(
- Enums.Events.ANNOTATION_ADDED,
- this.handleAnnotationAdded
- );
- // 注释修改事件
- eventTarget.addEventListener(
- Enums.Events.ANNOTATION_MODIFIED,
- this.handleAnnotationModified
- );
- // 注释删除事件
- eventTarget.addEventListener(
- Enums.Events.ANNOTATION_REMOVED,
- this.handleAnnotationRemoved
- );
- this.eventListenersRegistered = true;
- console.log('【annotationmanager】✅ 事件监听器注册完成');
- }
- private unregisterGlobalEventListeners(): void {
- if (!this.eventListenersRegistered) {
- return;
- }
- console.log('【annotationmanager】移除事件监听器...');
- eventTarget.removeEventListener(
- EVENTS.IMAGE_LOADED,
- this.handleImageLoadCompleted
- );
- eventTarget.removeEventListener(
- EVENTS.IMAGE_RENDERED,
- this.handleImageRendered
- );
- eventTarget.removeEventListener(
- Enums.Events.ANNOTATION_COMPLETED,
- this.handleAnnotationCompleted
- );
- eventTarget.removeEventListener(
- Enums.Events.ANNOTATION_ADDED,
- this.handleAnnotationCompleted
- );
- eventTarget.removeEventListener(
- Enums.Events.ANNOTATION_MODIFIED,
- this.handleAnnotationModified
- );
- eventTarget.removeEventListener(
- Enums.Events.ANNOTATION_REMOVED,
- this.handleAnnotationRemoved
- );
- this.eventListenersRegistered = false;
- console.log('【annotationmanager】✅ 事件监听器移除完成');
- }
- // ============ 事件处理方法 ============
- private handleImageLoadCompleted = (evt: any) => {
- const rawImageId = evt.detail.image?.imageId || evt.detail.imageId;
- console.log(`【annotationmanager】📸 原始图像ID: ${rawImageId}`);
- // 提取SOP Instance UID
- const sopInstanceUid = extractSopInstanceUid(rawImageId);
- if (!sopInstanceUid) {
- console.warn('【annotationmanager】无法从图像URL中提取SOP Instance UID:', rawImageId);
- return;
- }
- console.log(`【annotationmanager】📸 提取的SOP Instance UID: ${sopInstanceUid}`);
- // 使用提取的SOP Instance UID加载注释
- this.loadAnnotationsForImage(sopInstanceUid);
- };
- private handleImageRendered = (evt: any) => {
- const { viewportId } = evt.detail;
- console.log(`【annotationmanager】🎨 图像渲染完成: ${viewportId}`);
- // TODO: 确保注释正确显示
- };
- private handleAnnotationCompleted = (evt: any) => {
- console.log('🎯 监听到注释完成事件:', evt);
- console.log('📋 事件详情:', evt.detail);
- const { annotation, toolName } = evt.detail;
- console.log(`【annotationmanager】✏️ 注释创建完成: ${toolName}`);
- // 确保有活动的图像ID
- if (!this.activeImageId) {
- console.warn('【annotationmanager】没有活动的图像ID,跳过注释保存');
- return;
- }
- // 实现注释保存逻辑
- this.saveAnnotation(annotation, toolName);
- };
- private handleAnnotationAdded = (evt: any) => {
- console.log('【annotationmanager】🎯 监听到注释添加事件:', evt);
- console.log('【annotationmanager】📋 事件详情:', evt.detail);
- const { annotation } = evt.detail;
- const toolName = annotation.metadata?.toolName;
- console.log(`【annotationmanager】➕ 注释添加: ${toolName}`);
- // 确保有活动的图像ID
- if (!this.activeImageId) {
- console.warn('【annotationmanager】没有活动的图像ID,跳过注释保存');
- return;
- }
- // 实现注释保存逻辑
- this.saveAnnotation(annotation, toolName);
- };
- private handleAnnotationModified = (evt: any) => {
- console.log('🎯 监听到注释修改事件:', evt);
- console.log('📋 事件详情:', evt.detail);
- const { annotation, toolName } = evt.detail;
- console.log(`【annotationmanager】🔄 注释修改完成: ${toolName}`);
- // 确保有活动的图像ID
- if (!this.activeImageId) {
- console.warn('【annotationmanager】没有活动的图像ID,跳过注释更新');
- return;
- }
- // 实现注释更新逻辑
- this.updateAnnotation(annotation, toolName);
- };
- private handleAnnotationRemoved = (evt: any) => {
- console.log('🎯 监听到注释删除事件:', evt);
- console.log('📋 事件详情:', evt.detail);
- const { annotation } = evt.detail;
- const annotationUID = annotation.annotationUID;
- console.log(`【annotationmanager】🗑️ 注释删除完成: ${annotationUID}`);
- // 确保有活动的图像ID
- if (!this.activeImageId) {
- console.warn('【annotationmanager】没有活动的图像ID,跳过注释删除');
- return;
- }
- // 实现注释删除逻辑
- this.deleteAnnotation(annotation);
- };
- // ============ 注释操作方法 ============
- private async loadAnnotationsForImage(imageId: string): Promise<void> {
- if (this.isLoading) {
- console.log(`【annotationmanager】注释正在加载中,跳过: ${imageId}`);
- return;
- }
- this.isLoading = true;
- this.activeImageId = imageId;
- try {
- console.log(`【annotationmanager】📥 加载注释数据: ${imageId}`);
- // 调用API获取注释数据
- const response = await annotationAPI.getAnnotations(imageId);
- if (response.data && Array.isArray(response.data)) {
- const annotationStrings = response.data;
- // 验证注释数据
- // this.validator.validateAnnotationArray(annotationStrings);
- // 反序列化并渲染注释
- await this.renderAnnotations(annotationStrings);
- console.log(`【annotationmanager】✅ 注释加载完成: ${imageId} (${annotationStrings.length} 个注释)`);
- } else {
- console.log(`【annotationmanager】ℹ️ 图像 ${imageId} 没有注释数据`);
- }
- } catch (error) {
- console.error(`【annotationmanager】❌ 注释加载失败 ${imageId}:`, error);
- // 不抛出错误,让应用继续运行
- } finally {
- this.isLoading = false;
- }
- }
- private async saveAnnotation(annotation: any, toolName: string): Promise<void> {
- try {
- console.log(`【annotationmanager】💾 保存注释: ${toolName}`);
- // 序列化注释数据
- const serializedData = this.serializer.serialize(annotation, toolName);
- // 验证序列化数据
- // this.validator.validateAnnotationData(serializedData);
- // 添加到待保存队列(延迟保存)
- this.scheduleSave(serializedData);
- console.log(`【annotationmanager】✅ 注释保存任务已安排: ${toolName}`);
- } catch (error) {
- console.error(`【annotationmanager】❌ 注释保存失败 ${toolName}:`, error);
- }
- }
- private async updateAnnotation(annotation: any, toolName: string): Promise<void> {
- try {
- console.log(`【annotationmanager】🔄 更新注释: ${toolName}`);
- // 复用保存逻辑
- await this.saveAnnotation(annotation, toolName);
- console.log(`【annotationmanager】✅ 注释更新完成: ${toolName}`);
- } catch (error) {
- console.error(`【annotationmanager】❌ 注释更新失败 ${toolName}:`, error);
- }
- }
- private async deleteAnnotation(annotation: any): Promise<void> {
- try {
- const annotationUID = annotation.annotationUID;
- console.log(`【annotationmanager】🗑️ 删除注释: ${annotationUID}`);
- if (!this.activeImageId) {
- console.warn('【annotationmanager】没有活动的图像ID,跳过删除操作');
- return;
- }
- // 从 annotation 中获取 FrameOfReferenceUID
- const forUID = annotation.metadata?.referencedImageId;
- if (!forUID) {
- console.warn('【annotationmanager】FrameOfReferenceUID 未找到,跳过删除操作');
- return;
- }
- // 用 FrameOfReferenceUID 查找相关的 elements
- const elements = this.findElementsByFOR(forUID);
- // 收集删除后的所有注释
- const allAnnotationStrings: string[] = [];
- elements.forEach(element => {
- const anns = corAnnotation.state.getAnnotations('', element);
- if (anns) {
- // 序列化所有注释
- for (const [toolName, ann] of Object.entries(anns as any)) {
- try {
- const serialized = this.serializer.serialize(ann, toolName);
- allAnnotationStrings.push(serialized);
- } catch (error) {
- console.error('序列化注释失败:', error);
- // 跳过序列化失败的注释
- }
- }
- }
- });
- // 保存所有注释
- await annotationAPI.saveAnnotations(this.activeImageId, allAnnotationStrings);
- console.log(`【annotationmanager】✅ 注释删除完成: ${annotationUID} (剩余 ${allAnnotationStrings.length} 个注释)`);
- } catch (error) {
- console.error(`【annotationmanager】❌ 注释删除失败:`, error);
- }
- }
- /**
- * 通过 SOP Instance UID 检查 viewport 是否包含指定图像
- * @param viewport Stack viewport 实例
- * @param imageId 要检查的图像ID
- * @returns 是否包含该图像
- */
- private hasImageBySopInstanceUid(viewport: IStackViewport, imageId: string): boolean {
- try {
- // 1. 提取传入 imageId 的 SOP Instance UID
- const targetSopUid = extractSopInstanceUid(imageId);
- if (!targetSopUid) {
- console.warn('【annotationmanager】无法提取目标图像的 SOP Instance UID:', imageId);
- return false;
- }
- // 2. 获取 viewport 中的所有 imageIds
- const imageIds = viewport.getImageIds?.();
- if (!imageIds || imageIds.length === 0) {
- return false;
- }
- // 3. 遍历比较每个 imageId 的 SOP Instance UID
- for (const vpImageId of imageIds) {
- const vpSopUid = extractSopInstanceUid(vpImageId);
- if (vpSopUid && vpSopUid === targetSopUid) {
- return true;
- }
- }
- return false;
- } catch (error) {
- console.error('【annotationmanager】比较 SOP Instance UID 时出错:', error);
- return false;
- }
- }
- // 用 FrameOfReferenceUID 查找 viewport elements
- private findElementsByFOR(imageId: string): any[] {
- const elements: any[] = [];
- const renderingEngines = getRenderingEngines();
- if (renderingEngines) {
- renderingEngines.forEach((re: IRenderingEngine) => {
- re.getViewports().forEach((vp: IStackViewport) => {
- if (vp && vp.element) {
- const stackVp = vp as IStackViewport;
- if (this.hasImageBySopInstanceUid(stackVp, imageId)) {
- elements.push(vp.element);
- }
- }
- });
- });
- } else {
- console.warn('【annotationmanager】getRenderingEngines 方法不可用,无法查找元素');
- }
- return elements;
- }
- /**
- * 更新注释对象中的 URL,替换为当前服务器地址
- * @param annotation 注释对象
- * @returns 更新后的注释对象
- */
- private updateAnnotationUrls(annotation: any): any {
- try {
- const currentIpPort = getIpPort();
- if (!currentIpPort) {
- console.warn('【annotationmanager】无法获取当前服务器地址,跳过 URL 更新');
- return annotation;
- }
- // 正则表达式:匹配 URL 中的协议+域名/IP+端口部分
- // 例如:dicomweb:http://192.168.110.245:6001/path -> 提取 http://192.168.110.245:6001
- const urlRegex = /(https?:\/\/[^\/]+)/;
- // 更新 metadata.referencedImageId
- if (annotation.metadata?.referencedImageId) {
- const originalUrl = annotation.metadata.referencedImageId;
- const updatedUrl = originalUrl.replace(urlRegex, currentIpPort);
- annotation.metadata.referencedImageId = updatedUrl;
- console.log(`【annotationmanager】已更新 referencedImageId: ${originalUrl} -> ${updatedUrl}`);
- }
- // 更新 metadata.referencedImageURI(如果存在)
- if (annotation.metadata?.referencedImageURI) {
- const originalUrl = annotation.metadata.referencedImageURI;
- const updatedUrl = originalUrl.replace(urlRegex, currentIpPort);
- annotation.metadata.referencedImageURI = updatedUrl;
- console.log(`【annotationmanager】已更新 referencedImageURI: ${originalUrl} -> ${updatedUrl}`);
- }
- return annotation;
- } catch (error) {
- console.error('【annotationmanager】更新注释 URL 失败:', error);
- return annotation;
- }
- }
- private async renderAnnotations(annotationStrings: string[]): Promise<void> {
- console.log(`【annotationmanager】🎨 渲染 ${annotationStrings.length} 个注释`);
- for (const annotationString of annotationStrings) {
- try {
- // 反序列化为Cornerstone格式
- let deserializedAnnotation = this.serializer.deserialize(annotationString);
- // 更新注释中的 URL,替换为当前服务器地址
- deserializedAnnotation = this.updateAnnotationUrls(deserializedAnnotation);
- // 1. 从注释元数据中获取图像ID
- const imageId = deserializedAnnotation.metadata?.referencedImageId;
- if (!imageId) {
- console.warn('【annotationmanager】注释缺少 referencedImageId,跳过');
- continue;
- }
- // 2. 找到包含该图像的 viewport element
- const renderingEngines = getRenderingEngines();
- let targetElement: HTMLDivElement | null = null;
- if (renderingEngines) {
- for (const engine of renderingEngines) {
- const viewports = engine.getViewports();
- for (const viewport of viewports) {
- // 使用类型断言处理 IStackViewport
- const stackViewport = viewport as IStackViewport;
- if (stackViewport) {
- if (this.hasImageBySopInstanceUid(stackViewport, imageId)) {
- targetElement = stackViewport.element;
- break;
- }
- }
- }
- if (targetElement) break;
- }
- }
- if (!targetElement) {
- console.warn('【annotationmanager】未找到对应的 viewport,跳过');
- continue;
- }
- console.log(`【annotationmanager】渲染注释 ${JSON.stringify(deserializedAnnotation)} 到 imageid ${imageId} 的 viewport`);
- // 3. ✅ 使用正确的 element 参数添加注释
- corAnnotation.state.addAnnotation(deserializedAnnotation, targetElement);
- console.log(`【annotationmanager】✅ 注释渲染成功到 viewport`);
- } catch (error) {
- console.error(`【annotationmanager】❌ 注释渲染失败:`, error);
- // 继续处理其他注释
- }
- }
- }
- // ============ 延迟保存机制 ============
- /**
- * 安排延迟保存
- */
- private scheduleSave(annotationString: string): void {
- try {
- // 从JSON字符串中提取ID作为key
- const annotation = JSON.parse(annotationString);
- const key = annotation.annotationUID || annotation.id || `temp_${Date.now()}`;
- // 添加到待保存队列
- this.pendingChanges.set(key, annotationString);
- // 取消之前的延迟保存
- if (this.saveTimeout) {
- clearTimeout(this.saveTimeout);
- }
- // 设置新的延迟保存(1秒后)
- this.saveTimeout = setTimeout(() => {
- this.performBulkSave();
- }, 1000);
- } catch (error) {
- console.error('安排保存时解析注释失败:', error);
- }
- }
- /**
- * 执行批量保存
- */
- private async performBulkSave(): Promise<void> {
- if (this.pendingChanges.size === 0 || !this.activeImageId) {
- return;
- }
- const changes = Array.from(this.pendingChanges.values());
- this.pendingChanges.clear();
- try {
- console.log(`【annotationmanager】💾 批量保存 ${changes.length} 个注释`);
- // 调用API保存注释
- await annotationAPI.saveAnnotations(this.activeImageId, changes);
- console.log(`【annotationmanager】✅ 批量保存成功: ${changes.length} 个注释`);
- } catch (error) {
- console.error(`【annotationmanager】❌ 批量保存失败:`, error);
- // 保存失败时,重新加入待保存队列
- changes.forEach(change => {
- try {
- const annotation = JSON.parse(change);
- const key = annotation.annotationUID || annotation.id || `temp_${Date.now()}`;
- this.pendingChanges.set(key, change);
- } catch (error) {
- console.error('重新加入待保存队列时解析失败:', error);
- }
- });
- // 可以在这里实现重试逻辑
- }
- }
- }
|