소스 검색

添加动物专用测量工具--胫骨平台骨切开术

sw 2 일 전
부모
커밋
e96ca7edc5

+ 36 - 0
src/pages/view/components/ViewerContainer.tsx

@@ -34,6 +34,7 @@ import StackViewer, {
   deactivateHipDIMeasurement,
   clearHipDIMeasurements,
   activateHipNHAAngleMeasurement,
+  activateTPLOMeasurement,
 } from './viewers/stack.image.viewer';
 import { useSelector, useDispatch } from 'react-redux';
 import store, { RootState } from '@/states/store';
@@ -416,6 +417,7 @@ const ViewerContainer: React.FC<ViewerContainerProps> = ({ imageUrls }) => {
             MeasurementToolManager.clearHipDIMeasurementsForViewports(selectedViewportIds);
             MeasurementToolManager.clearHipNHAAngleMeasurementsForViewports(selectedViewportIds);
             MeasurementToolManager.clearVHSMeasurementsForViewports(selectedViewportIds);
+            MeasurementToolManager.clearTPLOMeasurementsForViewports(selectedViewportIds);
           }
           console.log('Clearing All Measurements from MeasurementPanel');
           break;
@@ -626,6 +628,40 @@ const ViewerContainer: React.FC<ViewerContainerProps> = ({ imageUrls }) => {
           console.log('Activating VHS Measurement from MeasurementPanel');
           break;
 
+        case '胫骨平台骨切开术':
+          console.log(`开始胫骨平台骨切开术测量`);
+          if (selectedViewportIds.length > 0) {
+            selectedViewportIds.forEach((viewportId) => {
+              const success = activateTPLOMeasurement(viewportId);
+              if (success) {
+                console.log(`激活TPLO测量工具成功`);
+                dispatch(
+                  setToolActive({
+                    toolName: 'TPLOMeasurementTool',
+                    viewportId: viewportId,
+                  })
+                );
+              }
+            });
+          } else {
+            // 如果没有选中的 viewport,为所有可见的 viewport 激活
+            const visibleViewportCount = getVisibleViewportCount();
+            for (let i = 0; i < visibleViewportCount; i++) {
+              const viewportId = `viewport-${i}`;
+              const success = activateTPLOMeasurement(viewportId);
+              if (success) {
+                dispatch(
+                  setToolActive({
+                    toolName: 'TPLOMeasurementTool',
+                    viewportId: viewportId,
+                  })
+                );
+              }
+            }
+          }
+          console.log('Activating TPLO Measurement from MeasurementPanel');
+          break;
+
         case '测量校正':
           console.log('Measurement Calibration - 功能待实现');
           break;

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

@@ -11,6 +11,7 @@ import DARAMeasurementTool from '@/components/measures/DARAMeasurementTool';
 import HipDIMeasurementTool from '@/components/measures/HipDIMeasurementTool';
 import HipNHAAngleMeasurementTool from '@/components/measures/HipNHAAngleMeasurementTool';
 import VHSMeasurementTool from '@/components/measures/VHSMeasurementTool';
+import TPLOMeasurementTool from '@/components/measures/TPLOMeasurementTool';
 import { boolean } from 'zod';
 
 const {
@@ -127,6 +128,7 @@ function registerTools(viewportId, renderingEngineId) {
   toolGroup.addTool(HipDIMeasurementTool.toolName); // 添加髋关节牵引指数测量工具
   toolGroup.addTool(HipNHAAngleMeasurementTool.toolName); // 添加髋关节水平角测量工具
   toolGroup.addTool(VHSMeasurementTool.toolName); // 添加心锥比测量工具
+  toolGroup.addTool(TPLOMeasurementTool.toolName); // 添加TPLO测量工具
 
   // 设置默认工具状态
   setupDefaultToolStates(toolGroup);
@@ -180,6 +182,7 @@ function setupDefaultToolStates(toolGroup: cornerstoneTools.Types.IToolGroup) {
   toolGroup.setToolPassive(HipDIMeasurementTool.toolName);
   toolGroup.setToolPassive(HipNHAAngleMeasurementTool.toolName);
   toolGroup.setToolPassive(VHSMeasurementTool.toolName);
+  toolGroup.setToolPassive(TPLOMeasurementTool.toolName);
 }
 export function addLMark(currentViewportId: string): void {
   // Implement the logic to add an L mark
@@ -866,6 +869,66 @@ export function isHipNHAAngleMeasurementActive(viewportId: string): boolean {
   return MeasurementToolManager.isHipNHAAngleMeasurementToolActive(viewportId);
 }
 
+// ==================== TPLO测量相关函数 ====================
+
+/**
+ * 激活TPLO测量工具
+ */
+export function activateTPLOMeasurement(viewportId: string): boolean {
+  console.log(
+    `[activateTPLOMeasurement] Activating TPLO measurement for viewport: ${viewportId}`
+  );
+  return MeasurementToolManager.activateTPLOMeasurementTool(viewportId);
+}
+
+/**
+ * 停用TPLO测量工具
+ */
+export function deactivateTPLOMeasurement(viewportId: string): boolean {
+  console.log(
+    `[deactivateTPLOMeasurement] Deactivating TPLO measurement for viewport: ${viewportId}`
+  );
+  return MeasurementToolManager.deactivateTPLOMeasurementTool(viewportId);
+}
+
+/**
+ * 切换TPLO测量工具状态
+ */
+export function toggleTPLOMeasurement(viewportId: string): boolean {
+  console.log(
+    `[toggleTPLOMeasurement] Toggling TPLO measurement for viewport: ${viewportId}`
+  );
+  return MeasurementToolManager.toggleTPLOMeasurementTool(viewportId);
+}
+
+/**
+ * 清除TPLO测量标注
+ */
+export function clearTPLOMeasurements(viewportId: string): boolean {
+  console.log(
+    `[clearTPLOMeasurements] Clearing TPLO measurements for viewport: ${viewportId}`
+  );
+  return MeasurementToolManager.clearTPLOMeasurements(viewportId);
+}
+
+/**
+ * 获取TPLO测量结果
+ */
+// eslint-disable-next-line
+export function getTPLOMeasurements(viewportId: string): any[] {
+  console.log(
+    `[getTPLOMeasurements] Getting TPLO measurements for viewport: ${viewportId}`
+  );
+  return MeasurementToolManager.getTPLOMeasurements(viewportId);
+}
+
+/**
+ * 检查TPLO测量工具是否激活
+ */
+export function isTPLOMeasurementActive(viewportId: string): boolean {
+  return MeasurementToolManager.isTPLOMeasurementToolActive(viewportId);
+}
+
 const StackViewer = ({
   imageIndex = 0,
   imageUrls = [],

+ 1 - 0
src/states/view/measurementPanelSlice.ts

@@ -12,6 +12,7 @@ export type MeasurementAction =
   | '髋关节水平角'
   | '髋关节牵引指数'
   | '心锥比'
+  | '胫骨平台骨切开术'
   | null;
 
 // 测量结果类型

+ 2 - 0
src/utils/cornerstoneToolsSetup.ts

@@ -4,6 +4,7 @@ import DARAMeasurementTool from '@/components/measures/DARAMeasurementTool';
 import HipDIMeasurementTool from '@/components/measures/HipDIMeasurementTool';
 import HipNHAAngleMeasurementTool from '@/components/measures/HipNHAAngleMeasurementTool';
 import VHSMeasurementTool from '@/components/measures/VHSMeasurementTool';
+import TPLOMeasurementTool from '@/components/measures/TPLOMeasurementTool';
 
 const {
   MagnifyTool,
@@ -48,6 +49,7 @@ export function registerGlobalTools(): void {
     cornerstoneTools.addTool(HipDIMeasurementTool); // 添加髋关节牵引指数测量工具
     cornerstoneTools.addTool(HipNHAAngleMeasurementTool); // 添加髋关节水平角测量工具
     cornerstoneTools.addTool(VHSMeasurementTool); // 添加心锥比测量工具
+    cornerstoneTools.addTool(TPLOMeasurementTool); // 添加胫骨平台骨切开术测量工具
 
     toolsRegistered = true;
     console.log('[cornerstoneToolsSetup] All tools registered successfully');

+ 272 - 0
src/utils/measurementToolManager.ts

@@ -5,6 +5,7 @@ import DARAMeasurementTool from '@/components/measures/DARAMeasurementTool';
 import HipDIMeasurementTool from '@/components/measures/HipDIMeasurementTool';
 import HipNHAAngleMeasurementTool from '@/components/measures/HipNHAAngleMeasurementTool';
 import VHSMeasurementTool from '@/components/measures/VHSMeasurementTool';
+import TPLOMeasurementTool from '@/components/measures/TPLOMeasurementTool';
 
 const {
   ToolGroupManager,
@@ -1745,4 +1746,275 @@ export class MeasurementToolManager {
       this.clearVHSMeasurements(viewportId)
     );
   }
+
+  // ==================== TPLO测量工具 ====================
+
+  /**
+   * 激活TPLO测量工具
+   */
+  static activateTPLOMeasurementTool(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.setToolPassive(AngleTool.toolName, {
+        removeAllBindings: true,
+      });
+      toolGroup.setToolPassive(TibialPlateauAngleTool.toolName, {
+        removeAllBindings: true,
+      });
+      toolGroup.setToolPassive(DARAMeasurementTool.toolName, {
+        removeAllBindings: true,
+      });
+      toolGroup.setToolPassive(HipDIMeasurementTool.toolName, {
+        removeAllBindings: true,
+      });
+      toolGroup.setToolPassive(HipNHAAngleMeasurementTool.toolName, {
+        removeAllBindings: true,
+      });
+      toolGroup.setToolPassive(VHSMeasurementTool.toolName, {
+        removeAllBindings: true,
+      });
+
+      // 激活TPLO测量工具
+      toolGroup.setToolActive(TPLOMeasurementTool.toolName, {
+        bindings: [{ mouseButton: MouseBindings.Primary }],
+      });
+
+      // 获取工具实例并激活修改模式
+      const toolInstance = toolGroup.getToolInstance(
+        TPLOMeasurementTool.toolName
+      ) as TPLOMeasurementTool;
+
+      const viewport = cornerstone.getEnabledElementByViewportId(viewportId)?.viewport;
+      if (toolInstance && viewport.element) {
+        toolInstance._activateModify(viewport.element);
+      }
+
+      // 自动创建一个预设的注解
+      try {
+        if (viewport && viewport.element) {
+          // 创建预设注解
+          const defaultAnnotation = TPLOMeasurementTool.createDefaultAnnotation(
+            viewport.element as HTMLDivElement,
+            viewport as cornerstone.Types.IStackViewport
+          );
+
+          // 添加注解到状态管理
+          cornerstoneTools.annotation.state.addAnnotation(
+            defaultAnnotation,
+            viewport.element
+          );
+
+          // 获取工具实例并更新缓存统计数据
+          const enabledElement = cornerstone.getEnabledElement(viewport.element);
+          if (enabledElement) {
+            const toolInstance = toolGroup.getToolInstance(
+              TPLOMeasurementTool.toolName
+            ) as TPLOMeasurementTool;
+
+            if (toolInstance && '_updateCachedStats' in toolInstance) {
+              (toolInstance as any)._updateCachedStats(defaultAnnotation, enabledElement);
+            }
+          }
+
+          // 触发渲染更新
+          viewport.render();
+
+          console.log('[MeasurementToolManager] Default TPLO annotation created successfully');
+        }
+      } catch (error) {
+        console.error('[MeasurementToolManager] Failed to create default TPLO annotation:', error);
+        // 注解创建失败不影响工具激活
+      }
+
+      console.log(
+        `[MeasurementToolManager] TPLO tool activated for viewport: ${viewportId}`
+      );
+      return true;
+    } catch (error) {
+      console.error(
+        `[MeasurementToolManager] Error activating TPLO tool:`,
+        error
+      );
+      return false;
+    }
+  }
+
+  /**
+   * 停用TPLO测量工具
+   */
+  static deactivateTPLOMeasurementTool(viewportId: string): boolean {
+    const toolGroup = this.getToolGroup(viewportId);
+    if (!toolGroup) return false;
+
+    try {
+      toolGroup.setToolPassive(TPLOMeasurementTool.toolName, {
+        removeAllBindings: true,
+      });
+      console.log(
+        `[MeasurementToolManager] TPLO tool deactivated for viewport: ${viewportId}`
+      );
+      return true;
+    } catch (error) {
+      console.error(
+        `[MeasurementToolManager] Error deactivating TPLO tool:`,
+        error
+      );
+      return false;
+    }
+  }
+
+  /**
+   * 检查TPLO测量工具是否处于激活状态
+   */
+  static isTPLOMeasurementToolActive(viewportId: string): boolean {
+    const toolGroup = this.getToolGroup(viewportId);
+    if (!toolGroup) return false;
+
+    try {
+      const activeTool = toolGroup.getActivePrimaryMouseButtonTool();
+      return activeTool === TPLOMeasurementTool.toolName;
+    } catch (error) {
+      console.error(
+        `[MeasurementToolManager] Error checking TPLO tool state:`,
+        error
+      );
+      return false;
+    }
+  }
+
+  /**
+   * 切换TPLO测量工具状态
+   */
+  static toggleTPLOMeasurementTool(viewportId: string): boolean {
+    const isActive = this.isTPLOMeasurementToolActive(viewportId);
+
+    if (isActive) {
+      return this.deactivateTPLOMeasurementTool(viewportId);
+    } else {
+      return this.activateTPLOMeasurementTool(viewportId);
+    }
+  }
+
+  /**
+   * 清除指定 viewport 的所有TPLO测量标注
+   */
+  static clearTPLOMeasurements(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(
+        TPLOMeasurementTool.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} TPLO measurements for viewport: ${viewportId}`
+      );
+      return true;
+    } catch (error) {
+      console.error(
+        `[MeasurementToolManager] Error clearing TPLO measurements:`,
+        error
+      );
+      return false;
+    }
+  }
+
+  /**
+   * 获取指定 viewport 的所有TPLO测量结果
+   */
+  // eslint-disable-next-line
+  static getTPLOMeasurements(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(
+        TPLOMeasurementTool.toolName,
+        viewport.element
+      );
+
+      return annotations.map((annotation) => ({
+        annotationUID: annotation.annotationUID,
+        tpa: annotation.data?.cachedStats?.tpa || 0,
+        sawRadiusPx: annotation.data?.cachedStats?.sawRadiusPx || 0,
+        sawRadiusMm: annotation.data?.cachedStats?.sawRadiusMm || 0,
+        unit: 'mixed',
+        points: annotation.data?.handles?.points || [],
+      }));
+    } catch (error) {
+      console.error(
+        `[MeasurementToolManager] Error getting TPLO measurements:`,
+        error
+      );
+      return [];
+    }
+  }
+
+  /**
+   * 为多个 viewport 批量激活TPLO测量工具
+   */
+  static activateTPLOMeasurementToolForViewports(
+    viewportIds: string[]
+  ): boolean[] {
+    return viewportIds.map((viewportId) =>
+      this.activateTPLOMeasurementTool(viewportId)
+    );
+  }
+
+  /**
+   * 为多个 viewport 批量停用TPLO测量工具
+   */
+  static deactivateTPLOMeasurementToolForViewports(
+    viewportIds: string[]
+  ): boolean[] {
+    return viewportIds.map((viewportId) =>
+      this.deactivateTPLOMeasurementTool(viewportId)
+    );
+  }
+
+  /**
+   * 为多个 viewport 批量清除TPLO测量
+   */
+  static clearTPLOMeasurementsForViewports(viewportIds: string[]): boolean[] {
+    return viewportIds.map((viewportId) =>
+      this.clearTPLOMeasurements(viewportId)
+    );
+  }
 }