فهرست منبع

feat: 实现角度测量工具功能

- 在 cornerstoneToolsSetup.ts 中添加 AngleTool 的全局注册
- 在 stack.image.viewer.tsx 中添加角度测量相关的导出函数和事件处理
- 在 ViewerContainer.tsx 中集成角度测量功能到测量面板操作
- 在 measurementToolManager.ts 中实现角度测量工具的完整管理逻辑
- 优化了工具激活和停用机制,确保工具间正确切换

改动文件:
- src/pages/view/components/ViewerContainer.tsx
- src/pages/view/components/viewers/stack.image.viewer.tsx
- src/utils/cornerstoneToolsSetup.ts
- src/utils/measurementToolManager.ts
sw 2 روز پیش
والد
کامیت
47b88cf988

+ 1 - 1
config/dev.ts

@@ -27,7 +27,7 @@ export default {
           // }
         },
       },
-      server: 'https',  // 启用 HTTPS ,为了开发环境测试打开摄像头功能
+      //server: 'https',  // 启用 HTTPS ,为了开发环境测试打开摄像头功能
       host: '0.0.0.0', // 监听所有网络接口
       open: false, // 可选:是否自动打开浏览器
       port: 10086, // 可选:指定端口号

+ 33 - 1
src/pages/view/components/ViewerContainer.tsx

@@ -21,6 +21,9 @@ import StackViewer, {
   activateLengthMeasurement,
   deactivateLengthMeasurement,
   clearLengthMeasurements,
+  activateAngleMeasurement,
+  deactivateAngleMeasurement,
+  clearAngleMeasurements,
 } from './viewers/stack.image.viewer';
 import { useSelector, useDispatch } from 'react-redux';
 import store, { RootState } from '@/states/store';
@@ -440,7 +443,36 @@ const ViewerContainer: React.FC<ViewerContainerProps> = ({ imageUrls }) => {
           break;
 
         case '角度测量':
-          console.log('Angle Measurement - 功能待实现');
+          console.log(`开始角度测量`);
+          if (selectedViewportIds.length > 0) {
+            selectedViewportIds.forEach((viewportId) => {
+              const success = activateAngleMeasurement(viewportId);
+              if (success) {
+                console.log(`激活角度测量工具成功`);
+                dispatch(
+                  setToolActive({
+                    toolName: 'AngleTool',
+                    viewportId: viewportId,
+                  })
+                );
+              }
+            });
+          } else {
+            // 如果没有选中的 viewport,为所有可见的 viewport 激活
+            const visibleViewportCount = getVisibleViewportCount();
+            for (let i = 0; i < visibleViewportCount; i++) {
+              const success = activateAngleMeasurement(`viewport-${i}`);
+              if (success) {
+                dispatch(
+                  setToolActive({
+                    toolName: 'AngleTool',
+                    viewportId: `viewport-${i}`,
+                  })
+                );
+              }
+            }
+          }
+          console.log('Activating Angle Measurement from MeasurementPanel');
           break;
 
         case '测量校正':

+ 63 - 0
src/pages/view/components/viewers/stack.image.viewer.tsx

@@ -16,6 +16,7 @@ const {
   ZoomTool,
   LabelTool,
   LengthTool,
+  AngleTool,//角度测量工具
   ToolGroupManager,
   Enums: csToolsEnums,
   PlanarRotateTool,
@@ -115,6 +116,7 @@ function registerTools(viewportId, renderingEngineId) {
   toolGroup.addTool(LabelTool.toolName);
   toolGroup.addTool(PlanarRotateTool.toolName);
   toolGroup.addTool(LengthTool.toolName); // 添加线段测量工具
+  toolGroup.addTool(AngleTool.toolName); // 添加角度测量工具
 
   // 设置默认工具状态
   setupDefaultToolStates(toolGroup);
@@ -162,6 +164,7 @@ function setupDefaultToolStates(toolGroup: cornerstoneTools.Types.IToolGroup) {
   toolGroup.setToolPassive(LabelTool.toolName);
   toolGroup.setToolPassive(PlanarRotateTool.toolName);
   toolGroup.setToolPassive(LengthTool.toolName);
+  toolGroup.setToolPassive(AngleTool.toolName);
 }
 export function addLMark(currentViewportId: string): void {
   // Implement the logic to add an L mark
@@ -548,6 +551,66 @@ export function isLengthMeasurementActive(viewportId: string): boolean {
   return MeasurementToolManager.isLengthToolActive(viewportId);
 }
 
+// ==================== 角度测量相关函数 ====================
+
+/**
+ * 激活角度测量工具
+ */
+export function activateAngleMeasurement(viewportId: string): boolean {
+  console.log(
+    `[activateAngleMeasurement] Activating angle measurement for viewport: ${viewportId}`
+  );
+  return MeasurementToolManager.activateAngleTool(viewportId);
+}
+
+/**
+ * 停用角度测量工具
+ */
+export function deactivateAngleMeasurement(viewportId: string): boolean {
+  console.log(
+    `[deactivateAngleMeasurement] Deactivating angle measurement for viewport: ${viewportId}`
+  );
+  return MeasurementToolManager.deactivateAngleTool(viewportId);
+}
+
+/**
+ * 切换角度测量工具状态
+ */
+export function toggleAngleMeasurement(viewportId: string): boolean {
+  console.log(
+    `[toggleAngleMeasurement] Toggling angle measurement for viewport: ${viewportId}`
+  );
+  return MeasurementToolManager.toggleAngleTool(viewportId);
+}
+
+/**
+ * 清除角度测量标注
+ */
+export function clearAngleMeasurements(viewportId: string): boolean {
+  console.log(
+    `[clearAngleMeasurements] Clearing angle measurements for viewport: ${viewportId}`
+  );
+  return MeasurementToolManager.clearAngleMeasurements(viewportId);
+}
+
+/**
+ * 获取角度测量结果
+ */
+// eslint-disable-next-line
+export function getAngleMeasurements(viewportId: string): any[] {
+  console.log(
+    `[getAngleMeasurements] Getting angle measurements for viewport: ${viewportId}`
+  );
+  return MeasurementToolManager.getAngleMeasurements(viewportId);
+}
+
+/**
+ * 检查角度测量工具是否激活
+ */
+export function isAngleMeasurementActive(viewportId: string): boolean {
+  return MeasurementToolManager.isAngleToolActive(viewportId);
+}
+
 const StackViewer = ({
   imageIndex = 0,
   imageUrls = [],

+ 1 - 0
src/utils/cornerstoneToolsSetup.ts

@@ -37,6 +37,7 @@ export function registerGlobalTools(): void {
     cornerstoneTools.addTool(LabelTool);
     cornerstoneTools.addTool(PlanarRotateTool);
     cornerstoneTools.addTool(LengthTool); // 添加线段测量工具
+    cornerstoneTools.addTool(cornerstoneTools.AngleTool); // 添加角度测量工具
 
     toolsRegistered = true;
     console.log('[cornerstoneToolsSetup] All tools registered successfully');

+ 197 - 0
src/utils/measurementToolManager.ts

@@ -4,6 +4,7 @@ import * as cornerstoneTools from '@cornerstonejs/tools';
 const {
   ToolGroupManager,
   LengthTool,
+  AngleTool,
   WindowLevelTool,
   MagnifyTool,
   Enums: csToolsEnums,
@@ -226,4 +227,200 @@ export class MeasurementToolManager {
       this.clearLengthMeasurements(viewportId)
     );
   }
+
+  /**
+   * 激活角度测量工具
+   */
+  static activateAngleTool(viewportId: string): boolean {
+    const toolGroup = this.getToolGroup(viewportId);
+    if (!toolGroup) return false;
+
+    try {
+      // 停用其他可能冲突的工具
+      toolGroup.setToolPassive(WindowLevelTool.toolName, {
+        removeAllBindings: true,
+      });
+      toolGroup.setToolPassive(MagnifyTool.toolName, {
+        removeAllBindings: true,
+      });
+      toolGroup.setToolPassive(LengthTool.toolName, {
+        removeAllBindings: true,
+      });
+
+      // 激活角度测量工具
+      toolGroup.setToolActive(AngleTool.toolName, {
+        bindings: [{ mouseButton: MouseBindings.Primary }],
+      });
+
+      console.log(
+        `[MeasurementToolManager] Angle tool activated for viewport: ${viewportId}`
+      );
+      return true;
+    } catch (error) {
+      console.error(
+        `[MeasurementToolManager] Error activating angle tool:`,
+        error
+      );
+      return false;
+    }
+  }
+
+  /**
+   * 停用角度测量工具
+   */
+  static deactivateAngleTool(viewportId: string): boolean {
+    const toolGroup = this.getToolGroup(viewportId);
+    if (!toolGroup) return false;
+
+    try {
+      toolGroup.setToolPassive(AngleTool.toolName, {
+        removeAllBindings: true,
+      });
+      console.log(
+        `[MeasurementToolManager] Angle tool deactivated for viewport: ${viewportId}`
+      );
+      return true;
+    } catch (error) {
+      console.error(
+        `[MeasurementToolManager] Error deactivating angle tool:`,
+        error
+      );
+      return false;
+    }
+  }
+
+  /**
+   * 检查角度测量工具是否处于激活状态
+   */
+  static isAngleToolActive(viewportId: string): boolean {
+    const toolGroup = this.getToolGroup(viewportId);
+    if (!toolGroup) return false;
+
+    try {
+      const activeTool = toolGroup.getActivePrimaryMouseButtonTool();
+      return activeTool === AngleTool.toolName;
+    } catch (error) {
+      console.error(
+        `[MeasurementToolManager] Error checking angle tool state:`,
+        error
+      );
+      return false;
+    }
+  }
+
+  /**
+   * 切换角度测量工具状态
+   */
+  static toggleAngleTool(viewportId: string): boolean {
+    const isActive = this.isAngleToolActive(viewportId);
+
+    if (isActive) {
+      return this.deactivateAngleTool(viewportId);
+    } else {
+      return this.activateAngleTool(viewportId);
+    }
+  }
+
+  /**
+   * 清除指定 viewport 的所有角度测量标注
+   */
+  static clearAngleMeasurements(viewportId: string): boolean {
+    try {
+      const viewport =
+        cornerstone.getEnabledElementByViewportId(viewportId)?.viewport;
+      if (!viewport) {
+        console.warn(
+          `[MeasurementToolManager] Viewport not found: ${viewportId}`
+        );
+        return false;
+      }
+
+      const annotations = cornerstoneTools.annotation.state.getAnnotations(
+        AngleTool.toolName,
+        viewport.element
+      );
+
+      let removedCount = 0;
+      annotations.forEach((annotation) => {
+        if (annotation.annotationUID) {
+          cornerstoneTools.annotation.state.removeAnnotation(
+            annotation.annotationUID
+          );
+          removedCount++;
+        }
+      });
+
+      viewport.render();
+      console.log(
+        `[MeasurementToolManager] Cleared ${removedCount} angle measurements for viewport: ${viewportId}`
+      );
+      return true;
+    } catch (error) {
+      console.error(
+        `[MeasurementToolManager] Error clearing angle measurements:`,
+        error
+      );
+      return false;
+    }
+  }
+
+  /**
+   * 获取指定 viewport 的所有角度测量结果
+   */
+  // eslint-disable-next-line
+  static getAngleMeasurements(viewportId: string): any[] {
+    try {
+      const viewport =
+        cornerstone.getEnabledElementByViewportId(viewportId)?.viewport;
+      if (!viewport) {
+        console.warn(
+          `[MeasurementToolManager] Viewport not found: ${viewportId}`
+        );
+        return [];
+      }
+
+      const annotations = cornerstoneTools.annotation.state.getAnnotations(
+        AngleTool.toolName,
+        viewport.element
+      );
+
+      return annotations.map((annotation) => ({
+        annotationUID: annotation.annotationUID,
+        angle: annotation.data?.cachedStats?.angle || 0,
+        unit: annotation.data?.cachedStats?.unit || 'degrees',
+        points: annotation.data?.handles?.points || [],
+      }));
+    } catch (error) {
+      console.error(
+        `[MeasurementToolManager] Error getting angle measurements:`,
+        error
+      );
+      return [];
+    }
+  }
+
+  /**
+   * 为多个 viewport 批量激活角度测量工具
+   */
+  static activateAngleToolForViewports(viewportIds: string[]): boolean[] {
+    return viewportIds.map((viewportId) => this.activateAngleTool(viewportId));
+  }
+
+  /**
+   * 为多个 viewport 批量停用角度测量工具
+   */
+  static deactivateAngleToolForViewports(viewportIds: string[]): boolean[] {
+    return viewportIds.map((viewportId) =>
+      this.deactivateAngleTool(viewportId)
+    );
+  }
+
+  /**
+   * 为多个 viewport 批量清除角度测量
+   */
+  static clearAngleMeasurementsForViewports(viewportIds: string[]): boolean[] {
+    return viewportIds.map((viewportId) =>
+      this.clearAngleMeasurements(viewportId)
+    );
+  }
 }