Просмотр исходного кода

fix: 修复注释渲染时基于SOP Instance UID的图像匹配问题

- 添加 hasImageBySopInstanceUid 方法,通过 SOP Instance UID 进行图像匹配
- 修改 renderAnnotations 方法,找到包含指定图像的正确 viewport element
- 修改 findElementsByFOR 方法,使用基于 SOP Instance UID 的比较逻辑
- 修复 TypeScript 类型检查警告
- 已解决:加载并渲染注释后不能二次编辑注释的问题
- 待解决:桌面端添加注释后,浏览器端不能显示注释的问题(仍在调查中)

改动文件:
- src/features/imageAnnotation/services/AnnotationManager.ts
- package.json (版本更新: 1.10.0 -> 1.10.1)
- CHANGELOG.md
dengdx 4 недель назад
Родитель
Сommit
28f9f13a9f
3 измененных файлов с 112 добавлено и 10 удалено
  1. 30 0
      CHANGELOG.md
  2. 1 1
      package.json
  3. 81 9
      src/features/imageAnnotation/services/AnnotationManager.ts

+ 30 - 0
CHANGELOG.md

@@ -5,6 +5,36 @@
 格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),
 版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
 
+## [1.10.1] - 2025-12-16 13:04
+
+### 修复 (Fixed)
+- **注释渲染时基于SOP Instance UID的图像匹配问题** ([src/features/imageAnnotation/services/AnnotationManager.ts](src/features/imageAnnotation/services/AnnotationManager.ts))
+  - 实现基于SOP Instance UID的图像比较逻辑,替代原有的直接字符串比较
+  - 新增 `hasImageBySopInstanceUid` 方法,通过提取和比较SOP Instance UID来判断图像是否匹配
+  - 修改 `renderAnnotations` 方法,正确查找包含指定图像的viewport element
+  - 修改 `findElementsByFOR` 方法,使用基于SOP Instance UID的比较逻辑
+  - 修复 TypeScript 类型检查警告,优化函数调用和条件判断
+  - 解决加载并渲染注释后不能二次编辑注释的问题
+
+**核心改进:**
+- 更准确的图像匹配:即使URL路径不同,只要SOP Instance UID相同就能正确匹配
+- 更好的兼容性:支持不同格式的DICOM URL(如dicomweb:前缀等)
+- 容错性增强:完善的错误处理和日志输出
+- 代码复用:利用现有的 `extractSopInstanceUid` 工具函数
+- 注释编辑能力:确保加载的注释可以被正确选中和编辑
+
+**技术实现:**
+- 使用可选链调用 `viewport.getImageIds?.()` 获取所有图像ID
+- 遍历比较每个imageId的SOP Instance UID
+- 优先级检测:文本框 → 手柄 → 线段 → 整体
+- 动态调整渲染参数,确保注释正确显示在对应的viewport
+
+**改动文件:**
+- src/features/imageAnnotation/services/AnnotationManager.ts
+- package.json (版本更新: 1.10.0 -> 1.10.1)
+
+---
+
 ## [1.10.0] - 2025-12-15 20:27
 
 ### 新增 (Added)

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "zsis",
-  "version": "1.10.0",
+  "version": "1.10.1",
   "private": true,
   "description": "医学成像系统",
   "main": "main.js",

+ 81 - 9
src/features/imageAnnotation/services/AnnotationManager.ts

@@ -357,15 +357,55 @@ export class AnnotationManager {
     }
   }
 
+  /**
+   * 通过 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[] = [];
-    if (!!getRenderingEngines) {
-      const renderingEngines = getRenderingEngines();
-      renderingEngines?.forEach((re: IRenderingEngine) => {
+    const renderingEngines = getRenderingEngines();
+    
+    if (renderingEngines) {
+      renderingEngines.forEach((re: IRenderingEngine) => {
         re.getViewports().forEach((vp: IStackViewport) => {
-          if (vp && vp.hasImageId(imageId) === true && vp.element) {
-            elements.push(vp.element);
+          if (vp && vp.element) {
+            const stackVp = vp as IStackViewport;
+            if (this.hasImageBySopInstanceUid(stackVp, imageId)) {
+              elements.push(vp.element);
+            }
           }
         });
       });
@@ -373,7 +413,6 @@ export class AnnotationManager {
       console.warn('【annotationmanager】getRenderingEngines 方法不可用,无法查找元素');
     }
 
-
     return elements;
   }
 
@@ -385,10 +424,43 @@ export class AnnotationManager {
         // 反序列化为Cornerstone格式
         const deserializedAnnotation = this.serializer.deserialize(annotationString);
 
-        // 添加到Cornerstone注释状态
-        corAnnotation.state.addAnnotation(deserializedAnnotation, deserializedAnnotation.metadata?.toolName || 'UnknownTool');
+        // 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;
+        }
+
+        // 3. ✅ 使用正确的 element 参数添加注释
+        corAnnotation.state.addAnnotation(deserializedAnnotation, targetElement);
 
-        console.log(`【annotationmanager】✅ 注释渲染成功`);
+        console.log(`【annotationmanager】✅ 注释渲染成功到 viewport`);
       } catch (error) {
         console.error(`【annotationmanager】❌ 注释渲染失败:`, error);
         // 继续处理其他注释