Browse Source

实现测量工具:找圆心

dengdx 3 tuần trước cách đây
mục cha
commit
bca3c9e02f

+ 1277 - 0
src/components/measures/CircleCenterMeasurementTool.ts

@@ -0,0 +1,1277 @@
+import { Point3, Point2 } from './mathUtils';
+import {
+  utilities as csUtils,
+  Types as CoreTypes,
+  getEnabledElement,
+} from '@cornerstonejs/core';
+
+import {
+  AnnotationTool,
+  utilities,
+  Types,
+  annotation,
+  drawing,
+} from '@cornerstonejs/tools';
+
+const {
+  drawHandles,
+  drawLine: drawLineSvg,
+  drawCircle: drawCircleSvg,
+} = drawing;
+
+import {
+  calculateMidpoint,
+  vectorSubtract,
+  vectorAdd,
+  getLineIntersection,
+} from './mathUtils';
+import {
+  PublicToolProps,
+  ToolProps,
+  EventTypes,
+  SVGDrawingHelper,
+} from '@cornerstonejs/tools/dist/esm/types';
+
+// 注解数据接口
+interface CircleCenterMeasurementAnnotation extends Types.Annotation {
+  data: {
+    handles: {
+      points: CoreTypes.Point3[];
+      activeHandleIndex: number | null;
+    };
+    cachedStats?: {
+      [targetId: string]: {
+        center: CoreTypes.Point3;   // 圆心坐标
+        radius: number;             // 圆半径
+        diameter: number;           // 圆直径
+      };
+    };
+  };
+  // 选中状态
+  isSelected?: boolean;
+}
+
+// 导出的注解数据接口
+export interface ExportedCircleCenterAnnotationData {
+  points: CoreTypes.Point3[];       // A和B两个点的世界坐标
+  circleInfo: {
+    center: CoreTypes.Point3;       // 圆心坐标
+    radius: number;                 // 圆半径
+    diameter: number;               // 圆直径
+  };
+  metadata: {
+    viewPlaneNormal: CoreTypes.Point3;
+    viewUp: CoreTypes.Point3;
+    FrameOfReferenceUID: string;
+    referencedImageId: string;
+  };
+}
+
+const cursorUrl='url("data:image/svg+xml;base64,PHN2ZyBpZD0ibW92ZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iNDgiIGhlaWdodD0iNDgiIHZpZXdCb3g9IjAgMCA0OCA0OCI+CiAgPGRlZnM+CiAgICA8c3R5bGU+CiAgICAgIC52aS1wcmltYXJ5IHsKICAgICAgICBmaWxsOiAjRkY2RTZFOwogICAgICB9CgogICAgICAudmktcHJpbWFyeSwgLnZpLWFjY2VudCB7CiAgICAgICAgZmlsbC1ydWxlOiBldmVub2RkOwogICAgICB9CgogICAgICAudmktYWNjZW50IHsKICAgICAgICBmaWxsOiAjMEMwMDU4OwogICAgICB9CiAgICA8L3N0eWxlPgogIDwvZGVmcz4KICA8cGF0aCBjbGFzcz0idmktcHJpbWFyeSIgZD0iTTM0LDMwbC0yLTIsMi0ySDI2djhsMi0yLDIsMi01LDVIMjRsLTUtNSwyLTItMiwyaDhWMTVsLTIsMi0yLTIsNS01aDFsNSw1LTIsMi0yLTJ2OGg4bC0yLTIsMi0yLDUsNXYxWiIvPgogIDxwYXRoIGNsYXNzPSJ2aS1hY2NlbnQiIGQ9Ik0xOSwyM3YzaDR2NGgzVjI2aDRWMjNIMjZWMTlIMjN2NEgxOVoiLz4KPC9zdmc+Cg==") 24 24, crosshair';
+
+export default class CircleCenterMeasurementTool extends AnnotationTool {
+  handleSelectedCallback(evt: EventTypes.InteractionEventType, annotation: Types.Annotation, handle: Types.ToolHandle, interactionType: Types.InteractionTypes): void {
+    const eventDetail = evt.detail;
+    const { element } = eventDetail;
+
+    const customAnnotation = annotation as CircleCenterMeasurementAnnotation;
+    customAnnotation.highlighted = true;
+
+    const viewportIdsToRender =
+      utilities.viewportFilters.getViewportIdsWithToolToRender(
+        element,
+        this.getToolName()
+      );
+
+    utilities.triggerAnnotationRenderForViewportIds(
+      viewportIdsToRender
+    );
+
+    evt.preventDefault();
+  }
+
+  toolSelectedCallback(evt: EventTypes.InteractionEventType, annotation: Types.Annotation, interactionType: Types.InteractionTypes, canvasCoords?: CoreTypes.Point2): void {
+    const eventDetail = evt.detail;
+    const { element } = eventDetail;
+
+    const customAnnotation = annotation as CircleCenterMeasurementAnnotation;
+    customAnnotation.isSelected = true;
+    customAnnotation.highlighted = true;
+
+    const viewportIdsToRender =
+      utilities.viewportFilters.getViewportIdsWithToolToRender(
+        element,
+        this.getToolName()
+      );
+
+    utilities.triggerAnnotationRenderForViewportIds(
+      viewportIdsToRender
+    );
+
+    evt.preventDefault();
+  }
+  static toolName = 'CircleCenterMeasurementTool';
+
+  editData: {
+    annotation: Types.Annotation;
+    viewportIdsToRender: string[];
+    handleIndex?: number;
+    newAnnotation?: boolean;
+    hasMoved?: boolean;
+    // 是否移动整个工具
+    movingWholeTool?: boolean;
+    // 移动整个工具时的初始偏移量
+    wholeToolOffset?: CoreTypes.Point2;
+    // 是否移动圆(通过圆环拖拽)
+    movingCircle?: boolean;
+    // 移动圆时的初始偏移量
+    circleOffset?: CoreTypes.Point2;
+  } | null = null;
+
+  isDrawing: boolean = false;
+
+  /**
+   * 创建一个预设的注解,带有两个默认点
+   */
+  static createDefaultAnnotation(
+    element: HTMLDivElement,
+    viewport: CoreTypes.IStackViewport | CoreTypes.IVolumeViewport
+  ): CircleCenterMeasurementAnnotation {
+    const enabledElement = getEnabledElement(element);
+    if (!enabledElement) {
+      throw new Error('Element is not enabled');
+    }
+
+    // 获取viewport的尺寸
+    const canvas = viewport.canvas;
+    const { width, height } = canvas;
+    const centerX = width / 2;
+    const centerY = height / 2;
+
+    // 创建两个点:A点在左侧,B点在右侧
+    const pointAX = centerX - 100;
+    const pointBX = centerX + 100;
+    const pointY = centerY;
+
+    const canvasPoints: CoreTypes.Point2[] = [
+      [pointAX, pointY],  // 点A
+      [pointBX, pointY],  // 点B
+    ];
+
+    // 转换为world坐标
+    const worldPoints = canvasPoints.map((canvasPoint) =>
+      viewport.canvasToWorld(canvasPoint)
+    );
+
+    const camera = viewport.getCamera();
+    const { viewPlaneNormal, viewUp } = camera;
+    if (viewPlaneNormal === undefined) {
+      throw new Error('viewPlaneNormal is undefined');
+    }
+    if (viewUp === undefined) {
+      throw new Error('viewUp is undefined');
+    }
+
+    const annotationData = {
+      invalidated: true,
+      highlighted: false,
+      metadata: {
+        viewPlaneNormal: [...viewPlaneNormal] as CoreTypes.Point3,
+        viewUp: [...viewUp] as CoreTypes.Point3,
+        FrameOfReferenceUID: viewport.getFrameOfReferenceUID(),
+        referencedImageId: viewport.getCurrentImageId?.() || '',
+        toolName: CircleCenterMeasurementTool.toolName,
+      },
+      data: {
+        label: '',
+        handles: {
+          points: worldPoints,
+          activeHandleIndex: null,
+        },
+        cachedStats: {},
+      },
+    } as CircleCenterMeasurementAnnotation;
+
+    return annotationData;
+  }
+
+  /**
+   * 导出注解数据
+   */
+  static exportAnnotationData(
+    annotation: CircleCenterMeasurementAnnotation,
+    viewport: CoreTypes.IStackViewport | CoreTypes.IVolumeViewport
+  ): ExportedCircleCenterAnnotationData | null {
+    const targetId = `imageId:${viewport.getCurrentImageId?.() || ''}`;
+    const cachedStats = annotation.data.cachedStats?.[targetId];
+
+    if (!cachedStats) {
+      return null;
+    }
+
+    return {
+      points: [...annotation.data.handles.points],
+      circleInfo: {
+        center: cachedStats.center,
+        radius: cachedStats.radius,
+        diameter: cachedStats.diameter,
+      },
+      metadata: {
+        viewPlaneNormal: annotation.metadata?.viewPlaneNormal || [0, 0, 1],
+        viewUp: annotation.metadata?.viewUp || [0, 1, 0],
+        FrameOfReferenceUID: annotation.metadata?.FrameOfReferenceUID || '',
+        referencedImageId: annotation.metadata?.referencedImageId || '',
+      },
+    };
+  }
+
+  /**
+   * 从导出的数据恢复注解
+   */
+  static restoreFromExportedData(
+    exportedData: ExportedCircleCenterAnnotationData,
+    element: HTMLDivElement,
+    viewport: CoreTypes.IStackViewport | CoreTypes.IVolumeViewport
+  ): CircleCenterMeasurementAnnotation {
+    const enabledElement = getEnabledElement(element);
+    if (!enabledElement) {
+      throw new Error('Element is not enabled');
+    }
+
+    const annotationData = {
+      invalidated: true,
+      highlighted: false,
+      metadata: {
+        ...exportedData.metadata,
+        toolName: CircleCenterMeasurementTool.toolName,
+      },
+      data: {
+        label: '',
+        handles: {
+          points: [...exportedData.points],
+          activeHandleIndex: null,
+        },
+        cachedStats: {},
+      },
+    } as CircleCenterMeasurementAnnotation;
+
+    return annotationData;
+  }
+
+  constructor(
+    toolProps: PublicToolProps = {},
+    defaultToolProps: ToolProps = {
+      supportedInteractionTypes: ['Mouse', 'Touch'],
+      configuration: {
+        shadow: true,
+        preventHandleOutsideImage: false,
+      },
+    }
+  ) {
+    super(toolProps, defaultToolProps);
+  }
+
+  /**
+   * 添加新注解 - 禁用此功能,因为我们只使用预设的注解
+   */
+  addNewAnnotation(
+    evt: EventTypes.InteractionEventType
+  ): CircleCenterMeasurementAnnotation {
+    evt.preventDefault();
+    return {} as CircleCenterMeasurementAnnotation;
+  }
+
+  /**
+   * 检查点是否靠近工具
+   */
+  isPointNearTool(
+    element: HTMLDivElement,
+    annotation: CircleCenterMeasurementAnnotation,
+    canvasCoords: CoreTypes.Point2,
+    proximity: number
+  ): boolean {
+    const enabledElement = getEnabledElement(element);
+    if (!enabledElement) {
+      return false;
+    }
+    const { viewport } = enabledElement;
+
+    const points = annotation.data.handles.points;
+
+    // 检查是否靠近任意一个手柄点
+    for (let i = 0; i < points.length; i++) {
+      const point = points[i];
+      const canvasPoint = viewport.worldToCanvas(point);
+      const distance = Math.sqrt(
+        Math.pow(canvasPoint[0] - canvasCoords[0], 2) +
+        Math.pow(canvasPoint[1] - canvasCoords[1], 2)
+      );
+
+      if (distance < proximity) {
+        return true;
+      }
+    }
+
+    // 检查是否靠近线段(A-B连线)
+    if (points.length >= 2) {
+      const p1Canvas = viewport.worldToCanvas(points[0]);
+      const p2Canvas = viewport.worldToCanvas(points[1]);
+
+      const dist = this._distanceToSegment(canvasCoords, p1Canvas, p2Canvas);
+      if (dist < proximity) {
+        return true;
+      }
+    }
+
+    // 检查是否靠近圆环
+    const targetId = this.getTargetId(viewport);
+    const cachedStats = targetId ? annotation.data.cachedStats?.[targetId] : undefined;
+    if (cachedStats?.center && cachedStats.radius > 0) {
+      const centerCanvas = viewport.worldToCanvas(cachedStats.center);
+      const distanceToCenter = Math.sqrt(
+        Math.pow(canvasCoords[0] - centerCanvas[0], 2) +
+        Math.pow(canvasCoords[1] - centerCanvas[1], 2)
+      );
+
+      // 检查是否在圆环附近(圆周 ± 一定容差)
+      const ringTolerance = 5;
+      if (Math.abs(distanceToCenter - cachedStats.radius) < ringTolerance) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+
+
+  /**
+   * 计算点到线段的距离
+   */
+  private _distanceToSegment(
+    point: CoreTypes.Point2,
+    lineStart: CoreTypes.Point2,
+    lineEnd: CoreTypes.Point2
+  ): number {
+    const x = point[0];
+    const y = point[1];
+    const x1 = lineStart[0];
+    const y1 = lineStart[1];
+    const x2 = lineEnd[0];
+    const y2 = lineEnd[1];
+
+    const A = x - x1;
+    const B = y - y1;
+    const C = x2 - x1;
+    const D = y2 - y1;
+
+    const dot = A * C + B * D;
+    const lenSq = C * C + D * D;
+    let param = -1;
+
+    if (lenSq !== 0) {
+      param = dot / lenSq;
+    }
+
+    let xx, yy;
+
+    if (param < 0) {
+      xx = x1;
+      yy = y1;
+    } else if (param > 1) {
+      xx = x2;
+      yy = y2;
+    } else {
+      xx = x1 + param * C;
+      yy = y1 + param * D;
+    }
+
+    const dx = x - xx;
+    const dy = y - yy;
+
+    return Math.sqrt(dx * dx + dy * dy);
+  }
+
+  /**
+   * 取消操作
+   */
+  cancel(element: HTMLDivElement): string {
+    if (this.isDrawing) {
+      this.isDrawing = false;
+      this._deactivateDraw(element);
+      this._deactivateModify(element);
+
+      const enabledElement = getEnabledElement(element);
+      if (enabledElement) {
+        const { renderingEngine } = enabledElement;
+        const viewportIdsToRender =
+          utilities.viewportFilters.getViewportIdsWithToolToRender(
+            element,
+            this.getToolName()
+          );
+
+        utilities.triggerAnnotationRenderForViewportIds(viewportIdsToRender);
+      }
+
+      this.editData = null;
+      return this.getToolName();
+    }
+
+    return '';
+  }
+
+  /**
+   * 激活绘制模式
+   */
+  _activateDraw(element: HTMLDivElement): void {
+    element.addEventListener(
+      'CORNERSTONE_TOOLS_MOUSE_DRAG',
+      this._dragCallback as EventListener
+    );
+    element.addEventListener(
+      'CORNERSTONE_TOOLS_MOUSE_UP',
+      this._endCallback as EventListener
+    );
+    element.addEventListener(
+      'CORNERSTONE_TOOLS_MOUSE_CLICK',
+      this._endCallback as EventListener
+    );
+  }
+
+  /**
+   * 取消激活绘制模式
+   */
+  _deactivateDraw(element: HTMLDivElement): void {
+    element.removeEventListener(
+      'CORNERSTONE_TOOLS_MOUSE_DRAG',
+      this._dragCallback as EventListener
+    );
+    element.removeEventListener(
+      'CORNERSTONE_TOOLS_MOUSE_UP',
+      this._endCallback as EventListener
+    );
+    element.removeEventListener(
+      'CORNERSTONE_TOOLS_MOUSE_CLICK',
+      this._endCallback as EventListener
+    );
+  }
+
+  /**
+   * 激活修改模式
+   */
+  _activateModify(element: HTMLDivElement): void {
+    element.addEventListener(
+      'CORNERSTONE_TOOLS_MOUSE_DOWN',
+      this._mouseDownModifyCallback as EventListener
+    );
+    element.addEventListener(
+      'CORNERSTONE_TOOLS_MOUSE_DRAG',
+      this._mouseDragModifyCallback as EventListener
+    );
+    element.addEventListener(
+      'CORNERSTONE_TOOLS_MOUSE_UP',
+      this._mouseUpModifyCallback as EventListener
+    );
+    element.addEventListener(
+      'CORNERSTONE_TOOLS_MOUSE_MOVE',
+      this._mouseMoveModifyCallback as EventListener
+    );
+    // 添加键盘事件监听用于删除功能
+    element.addEventListener(
+      'keydown',
+      this._keyDownCallback as EventListener
+    );
+  }
+
+  /**
+   * 取消激活修改模式
+   */
+  _deactivateModify(element: HTMLDivElement): void {
+    element.removeEventListener(
+      'CORNERSTONE_TOOLS_MOUSE_DOWN',
+      this._mouseDownModifyCallback as EventListener
+    );
+    element.removeEventListener(
+      'CORNERSTONE_TOOLS_MOUSE_DRAG',
+      this._mouseDragModifyCallback as EventListener
+    );
+    element.removeEventListener(
+      'CORNERSTONE_TOOLS_MOUSE_UP',
+      this._mouseUpModifyCallback as EventListener
+    );
+    element.removeEventListener(
+      'CORNERSTONE_TOOLS_MOUSE_MOVE',
+      this._mouseMoveModifyCallback as EventListener
+    );
+    // 移除键盘事件监听
+    element.removeEventListener(
+      'keydown',
+      this._keyDownCallback as EventListener
+    );
+  }
+
+  /**
+   * 键盘按下回调 - 用于删除功能
+   */
+  _keyDownCallback = (evt: KeyboardEvent): void => {
+    const element = evt.currentTarget as HTMLDivElement;
+
+    if (evt.key === 'Delete' || evt.key === 'Backspace') {
+      const enabledElement = getEnabledElement(element);
+      if (!enabledElement) {
+        return;
+      }
+
+      const annotations = annotation.state.getAnnotations(this.getToolName(), element);
+      if (!annotations || annotations.length === 0) {
+        return;
+      }
+
+      // 查找被选中的注解
+      const selectedAnnotations = annotations.filter(
+        (ann) => (ann as CircleCenterMeasurementAnnotation).isSelected
+      );
+
+      if (selectedAnnotations.length === 0) {
+        return;
+      }
+
+      // 删除所有选中的注解
+      for (const ann of selectedAnnotations) {
+        annotation.state.removeAnnotation(ann.annotationUID!);
+      }
+
+      // 触发渲染更新
+      const viewportIdsToRender =
+        utilities.viewportFilters.getViewportIdsWithToolToRender(
+          element,
+          this.getToolName()
+        );
+
+      utilities.triggerAnnotationRenderForViewportIds(viewportIdsToRender);
+
+      evt.preventDefault();
+      evt.stopPropagation();
+    }
+  };
+
+  /**
+   * 鼠标移动回调 - 用于修改模式,处理悬停检测
+   */
+  _mouseMoveModifyCallback = (evt: EventTypes.InteractionEventType): void => {
+    const eventDetail = evt.detail;
+    const { element, currentPoints } = eventDetail;
+
+    if (!currentPoints || !currentPoints.canvas) {
+      return;
+    }
+    const canvasCoords = currentPoints.canvas;
+
+    const enabledElement = getEnabledElement(element);
+    if (!enabledElement) {
+      return;
+    }
+    const { viewport } = enabledElement;
+
+    const annotations = annotation.state.getAnnotations(this.getToolName(), element);
+    if (!annotations || annotations.length === 0) {
+      element.style.cursor = 'default';
+      return;
+    }
+
+    let isHovering = false;
+
+    // 检查是否悬停在其他元素上
+    for (const ann of annotations) {
+      const customAnn = ann as CircleCenterMeasurementAnnotation;
+
+      // 检查是否靠近手柄
+      const handle = this.getHandleNearImagePoint(element, customAnn as Types.Annotation, canvasCoords, 6);
+      if (handle) {
+        element.style.cursor = cursorUrl;
+        customAnn.highlighted = true;
+        isHovering = true;
+        break;
+      }
+
+      // 检查是否靠近线段
+      if (this.isPointNearTool(element, customAnn, canvasCoords, 10)) {
+        element.style.cursor = cursorUrl;
+        customAnn.highlighted = true;
+        isHovering = true;
+        break;
+      }
+
+      // 检查是否靠近圆环
+      const targetId = this.getTargetId(viewport);
+      const cachedStats = targetId ? customAnn.data.cachedStats?.[targetId] : undefined;
+      if (cachedStats?.center && cachedStats.radius > 0) {
+        const centerCanvas = viewport.worldToCanvas(cachedStats.center);
+        const distanceToCenter = Math.sqrt(
+          Math.pow(canvasCoords[0] - centerCanvas[0], 2) +
+          Math.pow(canvasCoords[1] - centerCanvas[1], 2)
+        );
+
+        if (Math.abs(distanceToCenter - cachedStats.radius) < 5) {
+          element.style.cursor = cursorUrl;
+          customAnn.highlighted = true;
+          isHovering = true;
+          break;
+        }
+      }
+    }
+
+    // 如果没有悬停在任何地方,重置高亮
+    if (!isHovering) {
+      for (const ann of annotations) {
+        const customAnn = ann as CircleCenterMeasurementAnnotation;
+        if (!customAnn.isSelected) {
+          customAnn.highlighted = false;
+        }
+      }
+      element.style.cursor = 'default';
+    }
+
+    // 触发渲染以更新高亮状态
+    const viewportIdsToRender = utilities.viewportFilters.getViewportIdsWithToolToRender(
+      element,
+      this.getToolName()
+    );
+    utilities.triggerAnnotationRenderForViewportIds(viewportIdsToRender);
+  };
+
+  /**
+   * 鼠标按下回调 - 用于修改模式
+   */
+  _mouseDownModifyCallback = (evt: EventTypes.InteractionEventType): void => {
+    const eventDetail = evt.detail;
+    const { element, currentPoints } = eventDetail;
+    const canvasCoords = currentPoints.canvas;
+
+    const enabledElement = getEnabledElement(element);
+    if (!enabledElement) {
+      return;
+    }
+    const { viewport } = enabledElement;
+
+    const annotations = annotation.state.getAnnotations(this.getToolName(), element);
+    if (!annotations || annotations.length === 0) {
+      return;
+    }
+
+    // 检查是否点击在圆环上
+    for (const ann of annotations) {
+      const customAnn = ann as CircleCenterMeasurementAnnotation;
+      const targetId = this.getTargetId(viewport);
+      const cachedStats = targetId ? customAnn.data.cachedStats?.[targetId] : undefined;
+
+      if (cachedStats?.center && cachedStats.radius > 0) {
+        const centerCanvas = viewport.worldToCanvas(cachedStats.center);
+        const distanceToCenter = Math.sqrt(
+          Math.pow(canvasCoords[0] - centerCanvas[0], 2) +
+          Math.pow(canvasCoords[1] - centerCanvas[1], 2)
+        );
+
+        if (Math.abs(distanceToCenter - cachedStats.radius) < 5) {
+          const viewportIdsToRender =
+            utilities.viewportFilters.getViewportIdsWithToolToRender(
+              element,
+              this.getToolName()
+            );
+
+          const circleOffset: CoreTypes.Point2 = [
+            canvasCoords[0] - centerCanvas[0],
+            canvasCoords[1] - centerCanvas[1],
+          ];
+
+          this.editData = {
+            annotation: customAnn,
+            viewportIdsToRender,
+            hasMoved: false,
+            movingCircle: true,
+            circleOffset: circleOffset,
+          };
+
+          customAnn.isSelected = true;
+          customAnn.highlighted = true;
+          utilities.triggerAnnotationRenderForViewportIds(viewportIdsToRender);
+
+          evt.preventDefault();
+          evt.stopPropagation();
+          return;
+        }
+      }
+    }
+
+    // 查找最近的手柄
+    for (const ann of annotations) {
+      const customAnn = ann as CircleCenterMeasurementAnnotation;
+      const handle = this.getHandleNearImagePoint(
+        element,
+        customAnn,
+        canvasCoords,
+        6
+      );
+
+      if (handle) {
+        const viewportIdsToRender =
+          utilities.viewportFilters.getViewportIdsWithToolToRender(
+            element,
+            this.getToolName()
+          );
+
+        this.editData = {
+          annotation: customAnn,
+          viewportIdsToRender,
+          handleIndex: customAnn.data.handles.activeHandleIndex || 0,
+          hasMoved: false,
+        };
+
+        customAnn.isSelected = true;
+        customAnn.highlighted = true;
+
+        utilities.triggerAnnotationRenderForViewportIds(viewportIdsToRender);
+
+        evt.preventDefault();
+        evt.stopPropagation();
+        return;
+      }
+    }
+
+    // 检查是否点击在线段上
+    for (const ann of annotations) {
+      const customAnn = ann as CircleCenterMeasurementAnnotation;
+      if (this.isPointNearTool(element, customAnn, canvasCoords, 10)) {
+        const viewportIdsToRender =
+          utilities.viewportFilters.getViewportIdsWithToolToRender(
+            element,
+            this.getToolName()
+          );
+
+        // 计算相对于工具中心的偏移量
+        const points = customAnn.data.handles.points;
+        if (points.length >= 2) {
+          const center = calculateMidpoint(points[0], points[1]);
+          const centerCanvas = viewport.worldToCanvas(center);
+          const wholeToolOffset: CoreTypes.Point2 = [
+            canvasCoords[0] - centerCanvas[0],
+            canvasCoords[1] - centerCanvas[1],
+          ];
+
+          this.editData = {
+            annotation: customAnn,
+            viewportIdsToRender,
+            hasMoved: false,
+            movingWholeTool: true,
+            wholeToolOffset: wholeToolOffset,
+          };
+
+          customAnn.isSelected = true;
+          customAnn.highlighted = true;
+
+          utilities.triggerAnnotationRenderForViewportIds(viewportIdsToRender);
+
+          evt.preventDefault();
+          evt.stopPropagation();
+          return;
+        }
+      }
+    }
+
+    // 如果没有点击在工具上,取消所有工具的选中状态
+    for (const ann of annotations) {
+      const customAnn = ann as CircleCenterMeasurementAnnotation;
+      customAnn.isSelected = false;
+      customAnn.highlighted = false;
+    }
+
+    const viewportIdsToRender =
+      utilities.viewportFilters.getViewportIdsWithToolToRender(
+        element,
+        this.getToolName()
+      );
+
+    utilities.triggerAnnotationRenderForViewportIds(viewportIdsToRender);
+  };
+
+  /**
+   * 鼠标拖拽回调 - 用于修改模式
+   */
+  _mouseDragModifyCallback = (evt: EventTypes.InteractionEventType): void => {
+    if (!this.editData) {
+      return;
+    }
+
+    const eventDetail = evt.detail;
+    const { currentPoints } = eventDetail;
+    const canvasCoords = currentPoints.canvas;
+
+    const enabledElement = getEnabledElement(eventDetail.element);
+    if (!enabledElement) {
+      return;
+    }
+
+    const { annotation: ann, viewportIdsToRender, movingWholeTool, movingCircle } = this.editData;
+    const customAnn = ann as CircleCenterMeasurementAnnotation;
+    const { data } = customAnn;
+
+    // 如果正在移动圆
+    if (movingCircle && this.editData.circleOffset) {
+      const targetId = this.getTargetId(enabledElement.viewport);
+      const cachedStats = targetId ? data.cachedStats?.[targetId] : undefined;
+
+      if (cachedStats?.center) {
+        // 计算新的圆心位置
+        const newCenterCanvas: CoreTypes.Point2 = [
+          canvasCoords[0] - this.editData.circleOffset[0],
+          canvasCoords[1] - this.editData.circleOffset[1],
+        ];
+        const newCenterWorld = enabledElement.viewport.canvasToWorld(newCenterCanvas);
+
+        // 计算移动偏移量
+        const offset = vectorSubtract(newCenterWorld, cachedStats.center);
+
+        // 移动A和B两个点
+        if (data.handles.points.length >= 2) {
+          data.handles.points[0] = vectorAdd(data.handles.points[0], offset);
+          data.handles.points[1] = vectorAdd(data.handles.points[1], offset);
+        }
+
+        // 重新计算圆信息
+        this._updateCachedStats(customAnn, enabledElement);
+
+        this.editData.hasMoved = true;
+        utilities.triggerAnnotationRenderForViewportIds(viewportIdsToRender);
+
+        evt.preventDefault();
+        evt.stopPropagation();
+        return;
+      }
+    }
+
+    // 如果正在移动整个工具
+    if (movingWholeTool && this.editData.wholeToolOffset) {
+      const points = data.handles.points;
+      if (points.length >= 2) {
+        // 计算新的中心位置
+        const newCenterCanvas: CoreTypes.Point2 = [
+          canvasCoords[0] - this.editData.wholeToolOffset[0],
+          canvasCoords[1] - this.editData.wholeToolOffset[1],
+        ];
+        const newCenterWorld = enabledElement.viewport.canvasToWorld(newCenterCanvas);
+
+        const oldCenter = calculateMidpoint(points[0], points[1]);
+        const offset = vectorSubtract(newCenterWorld, oldCenter);
+
+        // 移动所有点
+        for (let i = 0; i < points.length; i++) {
+          points[i] = vectorAdd(points[i], offset);
+        }
+
+        // 重新计算圆信息
+        this._updateCachedStats(customAnn, enabledElement);
+
+        this.editData.hasMoved = true;
+        utilities.triggerAnnotationRenderForViewportIds(viewportIdsToRender);
+
+        evt.preventDefault();
+        evt.stopPropagation();
+        return;
+      }
+    }
+
+    // 否则处理手柄拖拽
+    const worldPos = currentPoints.world;
+    const activeHandleIndex = data.handles.activeHandleIndex;
+
+    if (activeHandleIndex !== null && activeHandleIndex >= 0 && activeHandleIndex < data.handles.points.length) {
+      data.handles.points[activeHandleIndex] = worldPos;
+
+      // 重新计算圆信息
+      this._updateCachedStats(customAnn, enabledElement);
+
+      this.editData.hasMoved = true;
+      utilities.triggerAnnotationRenderForViewportIds(viewportIdsToRender);
+
+      evt.preventDefault();
+      evt.stopPropagation();
+    }
+  };
+
+  /**
+   * 鼠标释放回调 - 用于修改模式
+   */
+  _mouseUpModifyCallback = (evt: EventTypes.InteractionEventType): void => {
+    if (!this.editData) {
+      return;
+    }
+
+    const { annotation: ann, hasMoved, movingWholeTool, movingCircle } = this.editData;
+    const customAnn = ann as CircleCenterMeasurementAnnotation;
+
+    customAnn.data.handles.activeHandleIndex = null;
+
+    // 如果没有拖拽且点击在线段上或圆环上,则进入选中状态
+    if (!hasMoved && (movingWholeTool || movingCircle)) {
+      customAnn.isSelected = true;
+      customAnn.highlighted = true;
+    }
+
+    const eventDetail = evt.detail;
+    const { element } = eventDetail;
+
+    const viewportIdsToRender =
+      utilities.viewportFilters.getViewportIdsWithToolToRender(
+        element,
+        this.getToolName()
+      );
+
+    utilities.triggerAnnotationRenderForViewportIds(viewportIdsToRender);
+
+    this.editData = null;
+
+    evt.preventDefault();
+    evt.stopPropagation();
+  };
+
+  /**
+   * 处理鼠标拖拽
+   */
+  _dragCallback = (evt: EventTypes.InteractionEventType): void => {
+    this.isDrawing = true;
+    const eventDetail = evt.detail;
+    const { element } = eventDetail;
+
+    const enabledElement = getEnabledElement(element);
+    if (!enabledElement || !this.editData) {
+      return;
+    }
+
+    const { annotation, viewportIdsToRender } = this.editData;
+    const { data } = annotation as CircleCenterMeasurementAnnotation;
+
+    if (annotation) {
+      const { currentPoints } = eventDetail;
+      const worldPos = currentPoints.world;
+
+      const points = data.handles.points;
+      const activeHandleIndex = data.handles.activeHandleIndex;
+
+      if (activeHandleIndex !== null && activeHandleIndex < points.length) {
+        points[activeHandleIndex] = worldPos;
+
+        this._updateCachedStats(
+          annotation as CircleCenterMeasurementAnnotation,
+          enabledElement
+        );
+
+        this.editData!.hasMoved = true;
+      } else if (this.editData!.newAnnotation) {
+        const lastIndex = points.length - 1;
+        if (lastIndex >= 0) {
+          points[lastIndex] = worldPos;
+        }
+      }
+
+      utilities.triggerAnnotationRenderForViewportIds(viewportIdsToRender);
+    }
+  };
+
+  /**
+   * 处理鼠标抬起/点击
+   */
+  _endCallback = (evt: EventTypes.InteractionEventType): void => {
+    const eventDetail = evt.detail;
+    const { element } = eventDetail;
+
+    if (!this.editData) {
+      return;
+    }
+
+    const { annotation, viewportIdsToRender, newAnnotation, hasMoved } =
+      this.editData;
+    const { data } = annotation as CircleCenterMeasurementAnnotation;
+
+    if (newAnnotation && !hasMoved) {
+      const points = data.handles.points;
+
+      if (points.length < 2) {
+        const { currentPoints } = eventDetail;
+        const worldPos = currentPoints.world;
+
+        if (points.length > 0) {
+          points[points.length - 1] = worldPos;
+        }
+
+        if (points.length < 2) {
+          points.push(worldPos);
+          data.handles.activeHandleIndex = points.length - 1;
+        }
+
+        if (points.length === 2) {
+          this.isDrawing = false;
+          data.handles.activeHandleIndex = null;
+          annotation.highlighted = false;
+          this.editData.newAnnotation = false;
+          this._deactivateDraw(element);
+        }
+
+        const enabledElement = getEnabledElement(element);
+        if (enabledElement) {
+          this._updateCachedStats(
+            annotation as CircleCenterMeasurementAnnotation,
+            enabledElement
+          );
+
+          const { renderingEngine } = enabledElement;
+          const newViewportIdsToRender =
+            utilities.viewportFilters.getViewportIdsWithToolToRender(
+              element,
+              this.getToolName()
+            );
+          utilities.triggerAnnotationRenderForViewportIds(
+            newViewportIdsToRender
+          );
+        }
+      }
+    } else if (hasMoved) {
+      this.editData.hasMoved = false;
+    }
+  };
+
+  /**
+   * 获取目标ID
+   */
+  getTargetId(viewport: CoreTypes.IStackViewport | CoreTypes.IVolumeViewport): string | undefined {
+    return `imageId:${viewport.getCurrentImageId?.() || ''}`;
+  }
+
+  /**
+   * 获取靠近图像点的手柄
+   */
+  getHandleNearImagePoint(
+    element: HTMLDivElement,
+    annotation: Types.Annotation,
+    canvasCoords: CoreTypes.Point2,
+    proximity: number
+  ): Types.ToolHandle | undefined {
+    const enabledElement = getEnabledElement(element);
+    if (!enabledElement) {
+      return undefined;
+    }
+    const { viewport } = enabledElement;
+
+    const customAnnotation = annotation as CircleCenterMeasurementAnnotation;
+    const points = customAnnotation.data.handles.points;
+    const handleProximity = Math.max(proximity, 15);
+
+    for (let i = 0; i < points.length; i++) {
+      const point = points[i];
+      const canvasPoint = viewport.worldToCanvas(point);
+      const distance = Math.sqrt(
+        Math.pow(canvasPoint[0] - canvasCoords[0], 2) +
+        Math.pow(canvasPoint[1] - canvasCoords[1], 2)
+      );
+
+      if (distance < handleProximity) {
+        customAnnotation.data.handles.activeHandleIndex = i;
+        return {
+          worldPosition: point,
+        } as Types.ToolHandle;
+      }
+    }
+
+    customAnnotation.data.handles.activeHandleIndex = null;
+    return undefined;
+  }
+
+  /**
+   * 更新缓存的统计数据
+   */
+  _updateCachedStats(
+    annotation: CircleCenterMeasurementAnnotation,
+    enabledElement: CoreTypes.IEnabledElement
+  ): void {
+    const { viewport } = enabledElement;
+    const { data } = annotation;
+    const points = data.handles.points;
+
+    const targetId = this.getTargetId(viewport);
+    if (!targetId) {
+      return;
+    }
+
+    if (!data.cachedStats) {
+      data.cachedStats = {};
+    }
+
+    if (!data.cachedStats[targetId]) {
+      data.cachedStats[targetId] = {
+        center: [0, 0, 0] as CoreTypes.Point3,
+        radius: 0,
+        diameter: 0,
+      };
+    }
+
+    const stats = data.cachedStats[targetId];
+
+    if (points.length >= 2) {
+      // 计算圆心:A和B的中点
+      const center = calculateMidpoint(points[0], points[1]);
+
+      // 计算直径:A和B之间的距离
+      const diameter = Math.sqrt(
+        Math.pow(points[1][0] - points[0][0], 2) +
+        Math.pow(points[1][1] - points[0][1], 2) +
+        Math.pow(points[1][2] - points[0][2], 2)
+      );
+
+      // 计算半径
+      const radius = diameter / 2;
+
+      // 存储计算结果
+      stats.center = center;
+      stats.radius = radius;
+      stats.diameter = diameter;
+    }
+  }
+
+  /**
+   * 渲染注解
+   */
+  renderAnnotation = (
+    enabledElement: CoreTypes.IEnabledElement,
+    svgDrawingHelper: SVGDrawingHelper
+  ): boolean => {
+    let renderStatus = false;
+    const { viewport } = enabledElement;
+    const { element } = viewport;
+
+    let annotations = annotation.state.getAnnotations(this.getToolName(), element);
+
+    if (!annotations?.length) {
+      return renderStatus;
+    }
+
+    for (let i = 0; i < annotations.length; i++) {
+      const annotation = annotations[i] as CircleCenterMeasurementAnnotation;
+      const { annotationUID, data } = annotation;
+      const points = data.handles.points;
+      const activeHandleIndex = data.handles.activeHandleIndex;
+
+      if (points.length < 2) {
+        continue;
+      }
+
+      const targetId = this.getTargetId(viewport);
+      const cachedStats = targetId ? data.cachedStats?.[targetId] : undefined;
+
+      // 转换点为canvas坐标
+      const canvasPoints = points.map((p) => viewport.worldToCanvas(p));
+
+      // 绘制A-B连线
+      if (annotationUID) {
+        const lineUID = `${annotationUID}-line`;
+        const lineOptions = {
+          color: 'rgb(0, 255, 0)', // 绿色线段
+          width: 2,
+        };
+
+        drawLineSvg(
+          svgDrawingHelper,
+          annotationUID,
+          lineUID,
+          canvasPoints[0],
+          canvasPoints[1],
+          lineOptions
+        );
+      }
+
+      // 绘制圆
+      if (cachedStats?.center && cachedStats.radius > 0 && annotationUID) {
+        const centerCanvas = viewport.worldToCanvas(cachedStats.center);
+        const circleUID = `${annotationUID}-circle`;
+
+        // 计算canvas坐标系中的半径(像素距离)
+        const radiusCanvas = Math.sqrt(
+          Math.pow(canvasPoints[0][0] - centerCanvas[0], 2) +
+          Math.pow(canvasPoints[0][1] - centerCanvas[1], 2)
+        );
+
+        drawCircleSvg(
+          svgDrawingHelper,
+          annotationUID,
+          circleUID,
+          centerCanvas,
+          radiusCanvas,
+          {
+            color: 'rgb(255, 0, 0)', // 红色圆
+            width: 2,
+            fill: 'transparent',
+          }
+        );
+
+        // 绘制圆心标记
+        const centerMarkUID = `${annotationUID}-center`;
+        drawCircleSvg(
+          svgDrawingHelper,
+          annotationUID,
+          centerMarkUID,
+          centerCanvas,
+          4,
+          {
+            color: 'rgb(255, 0, 0)',
+            fill: 'rgb(255, 0, 0)',
+          }
+        );
+
+        // 绘制圆心十字标记
+        const crossSize = 6;
+        const crossUID1 = `${annotationUID}-cross1`;
+        const crossUID2 = `${annotationUID}-cross2`;
+
+        drawLineSvg(
+          svgDrawingHelper,
+          annotationUID,
+          crossUID1,
+          [centerCanvas[0] - crossSize, centerCanvas[1]],
+          [centerCanvas[0] + crossSize, centerCanvas[1]],
+          { color: 'rgb(255, 0, 0)', width: 1 }
+        );
+
+        drawLineSvg(
+          svgDrawingHelper,
+          annotationUID,
+          crossUID2,
+          [centerCanvas[0], centerCanvas[1] - crossSize],
+          [centerCanvas[0], centerCanvas[1] + crossSize],
+          { color: 'rgb(255, 0, 0)', width: 1 }
+        );
+      }
+
+
+
+      // 绘制手柄点
+      if (annotationUID) {
+        const handleGroupUID = '0';
+        const isSelected = annotation.isSelected || annotation.highlighted;
+        const handleRadius = isSelected ? 12 : 8;
+
+        drawHandles(
+          svgDrawingHelper,
+          annotationUID,
+          handleGroupUID,
+          canvasPoints,
+          {
+            color: 'rgb(255, 255, 255)',
+            handleRadius: handleRadius,
+          }
+        );
+      }
+
+      renderStatus = true;
+    }
+
+    return renderStatus;
+  };
+}

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

@@ -32,6 +32,7 @@ import StackViewer, {
   activateCBLOMeasurement,
   activateHipCoverageMeasurement,
   activateHipDorsalCoverageMeasurement,
+  activateCircleCenterMeasurement,
 } from './viewers/stack.image.viewer';
 import { useSelector, useDispatch } from 'react-redux';
 import { getDcmImageUrl } from '@/API/bodyPosition';
@@ -118,6 +119,11 @@ const MEASUREMENT_TOOL_CONFIGS: Record<string, MeasurementToolConfig> = {
     activateFunction: activateHipDorsalCoverageMeasurement,
     logPrefix: 'HipDorsalCoverage',
   },
+  '找圆心': {
+    toolName: 'CircleCenterMeasurementTool',
+    activateFunction: activateCircleCenterMeasurement,
+    logPrefix: 'CircleCenter',
+  },
 };
 
 const setup = () => {
@@ -569,6 +575,7 @@ const ViewerContainer: React.FC<ViewerContainerProps> = ({ imageUrls }) => {
             MeasurementToolManager.clearCBLOMeasurementsForViewports(selectedViewportIds);
             MeasurementToolManager.clearHipCoverageMeasurementsForViewports(selectedViewportIds);
             MeasurementToolManager.clearHipDorsalCoverageMeasurementsForViewports(selectedViewportIds);
+            MeasurementToolManager.clearCircleCenterMeasurementsForViewports(selectedViewportIds);
           }
           console.log('Clearing All Measurements from MeasurementPanel');
           break;
@@ -618,7 +625,8 @@ const ViewerContainer: React.FC<ViewerContainerProps> = ({ imageUrls }) => {
         case '胫骨结节前移术':
         case '水平截骨术':
         case '股骨头覆盖率':
-        case '髋臼背覆盖': {
+        case '髋臼背覆盖':
+        case '找圆心': {
           const config = MEASUREMENT_TOOL_CONFIGS[measurementAction];
           if (config) {
             activateMeasurementTool(config, selectedViewportIds);

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

@@ -16,6 +16,7 @@ import TTAMeasurementTool from '@/components/measures/TTAMeasurementTool';
 import CBLOMeasurementTool from '@/components/measures/CBLOMeasurementTool';
 import HipCoverageMeasurementTool from '@/components/measures/HipCoverageMeasurementTool';
 import HipDorsalCoverageTool from '@/components/measures/HipDorsalCoverageTool';
+import CircleCenterMeasurementTool from '@/components/measures/CircleCenterMeasurementTool';
 import DicomOverlayTool from '@/components/overlay/DicomOverlayTool';
 import ImageViewerErrorBoundary from './ImageViewerErrorBoundary';
 import { boolean } from 'zod';
@@ -200,6 +201,7 @@ function registerTools(viewportId, renderingEngineId) {
   toolGroup.addTool(CBLOMeasurementTool.toolName); // 添加CBLO测量工具
   toolGroup.addTool(HipCoverageMeasurementTool.toolName); // 添加股骨头覆盖率测量工具
   toolGroup.addTool(HipDorsalCoverageTool.toolName); // 添加髋臼背覆盖测量工具
+  toolGroup.addTool(CircleCenterMeasurementTool.toolName); // 添加找圆心测量工具
   toolGroup.addTool(DicomOverlayTool.toolName); // 添加DICOM四角信息显示工具
 
   // 设置默认工具状态
@@ -259,6 +261,7 @@ function setupDefaultToolStates(toolGroup: cornerstoneTools.Types.IToolGroup) {
   toolGroup.setToolPassive(CBLOMeasurementTool.toolName);
   toolGroup.setToolPassive(HipCoverageMeasurementTool.toolName);
   toolGroup.setToolPassive(HipDorsalCoverageTool.toolName);
+  toolGroup.setToolPassive(CircleCenterMeasurementTool.toolName);
 }
 export function addLMark(currentViewportId: string): void {
   // Implement the logic to add an L mark
@@ -1187,6 +1190,23 @@ export function clearHipDorsalCoverageMeasurements(viewportId: string): boolean
   console.log(`[clearHipDorsalCoverageMeasurements] Clearing HipDorsalCoverage measurements for viewport: ${viewportId}`);
   return MeasurementToolManager.clearHipDorsalCoverageMeasurements(viewportId);
 }
+
+// ==================== 找圆心测量相关函数 ====================
+
+export function activateCircleCenterMeasurement(viewportId: string): boolean {
+  console.log(`[activateCircleCenterMeasurement] Activating CircleCenter measurement for viewport: ${viewportId}`);
+  return MeasurementToolManager.activateCircleCenterMeasurementTool(viewportId);
+}
+
+export function deactivateCircleCenterMeasurement(viewportId: string): boolean {
+  console.log(`[deactivateCircleCenterMeasurement] Deactivating CircleCenter measurement for viewport: ${viewportId}`);
+  return MeasurementToolManager.deactivateCircleCenterMeasurementTool(viewportId);
+}
+
+export function clearCircleCenterMeasurements(viewportId: string): boolean {
+  console.log(`[clearCircleCenterMeasurements] Clearing CircleCenter measurements for viewport: ${viewportId}`);
+  return MeasurementToolManager.clearCircleCenterMeasurements(viewportId);
+}
 export class ImageLoadError extends Error {
   constructor(message: string, failedImageIds: string[]) {
     super(message);

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

@@ -17,6 +17,7 @@ export type MeasurementAction =
   | '水平截骨术'
   | '股骨头覆盖率'
   | '髋臼背覆盖'
+  | '找圆心'
   | null;
 
 // 测量结果类型

+ 2 - 0
src/utils/cornerstoneToolsSetup.ts

@@ -9,6 +9,7 @@ import TTAMeasurementTool from '@/components/measures/TTAMeasurementTool';
 import CBLOMeasurementTool from '@/components/measures/CBLOMeasurementTool';
 import HipCoverageMeasurementTool from '@/components/measures/HipCoverageMeasurementTool';
 import HipDorsalCoverageTool from '@/components/measures/HipDorsalCoverageTool';
+import CircleCenterMeasurementTool from '@/components/measures/CircleCenterMeasurementTool';
 import DicomOverlayTool from '@/components/overlay/DicomOverlayTool';
 
 const {
@@ -59,6 +60,7 @@ export function registerGlobalTools(): void {
     cornerstoneTools.addTool(CBLOMeasurementTool); // 添加水平截骨术测量工具
     cornerstoneTools.addTool(HipCoverageMeasurementTool); // 添加股骨头覆盖率测量工具
     cornerstoneTools.addTool(HipDorsalCoverageTool); // 添加髋臼背覆盖测量工具
+    cornerstoneTools.addTool(CircleCenterMeasurementTool); // 添加找圆心测量工具
     cornerstoneTools.addTool(DicomOverlayTool); // 添加DICOM四角信息显示工具
 
     toolsRegistered = true;

+ 112 - 0
src/utils/measurementToolManager.ts

@@ -10,6 +10,7 @@ import TTAMeasurementTool from '@/components/measures/TTAMeasurementTool';
 import CBLOMeasurementTool from '@/components/measures/CBLOMeasurementTool';
 import HipCoverageMeasurementTool from '@/components/measures/HipCoverageMeasurementTool';
 import HipDorsalCoverageTool from '@/components/measures/HipDorsalCoverageTool';
+import CircleCenterMeasurementTool from '@/components/measures/CircleCenterMeasurementTool';
 
 const {
   ToolGroupManager,
@@ -2617,4 +2618,115 @@ export class MeasurementToolManager {
   static clearHipDorsalCoverageMeasurementsForViewports(viewportIds: string[]): boolean[] {
     return viewportIds.map((viewportId) => this.clearHipDorsalCoverageMeasurements(viewportId));
   }
+
+  // ==================== CircleCenter(找圆心)测量工具 ====================
+
+  static activateCircleCenterMeasurementTool(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 });
+      toolGroup.setToolPassive(TPLOMeasurementTool.toolName, { removeAllBindings: true });
+      toolGroup.setToolPassive(TTAMeasurementTool.toolName, { removeAllBindings: true });
+      toolGroup.setToolPassive(CBLOMeasurementTool.toolName, { removeAllBindings: true });
+      toolGroup.setToolPassive(HipCoverageMeasurementTool.toolName, { removeAllBindings: true });
+      toolGroup.setToolPassive(HipDorsalCoverageTool.toolName, { removeAllBindings: true });
+
+      toolGroup.setToolActive(CircleCenterMeasurementTool.toolName, {
+        bindings: [{ mouseButton: MouseBindings.Primary }],
+      });
+
+      const toolInstance = toolGroup.getToolInstance(CircleCenterMeasurementTool.toolName) as CircleCenterMeasurementTool;
+      const viewport = cornerstone.getEnabledElementByViewportId(viewportId)?.viewport;
+      if (toolInstance && viewport.element) {
+        toolInstance._activateModify(viewport.element);
+      }
+
+      try {
+        if (viewport && viewport.element) {
+          const defaultAnnotation = CircleCenterMeasurementTool.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(CircleCenterMeasurementTool.toolName) as CircleCenterMeasurementTool;
+            if (toolInstance && '_updateCachedStats' in toolInstance) {
+              (toolInstance as any)._updateCachedStats(defaultAnnotation, enabledElement);
+            }
+          }
+
+          // 触发渲染更新
+viewport.render();
+
+          console.log('[MeasurementToolManager] Default CircleCenter annotation created successfully');
+        }
+      } catch (error) {
+        console.error('[MeasurementToolManager] Failed to create default CircleCenter annotation:', error);
+      }
+
+      console.log(`[MeasurementToolManager] CircleCenter tool activated for viewport: ${viewportId}`);
+      return true;
+    } catch (error) {
+      console.error(`[MeasurementToolManager] Error activating CircleCenter tool:`, error);
+      return false;
+    }
+  }
+
+  static deactivateCircleCenterMeasurementTool(viewportId: string): boolean {
+    const toolGroup = this.getToolGroup(viewportId);
+    if (!toolGroup) return false;
+
+    try {
+      toolGroup.setToolPassive(CircleCenterMeasurementTool.toolName, { removeAllBindings: true });
+      console.log(`[MeasurementToolManager] CircleCenter tool deactivated for viewport: ${viewportId}`);
+      return true;
+    } catch (error) {
+      console.error(`[MeasurementToolManager] Error deactivating CircleCenter tool:`, error);
+      return false;
+    }
+  }
+
+  static clearCircleCenterMeasurements(viewportId: string): boolean {
+    try {
+      const viewport = cornerstone.getEnabledElementByViewportId(viewportId)?.viewport;
+      if (!viewport) return false;
+
+      const annotations = cornerstoneTools.annotation.state.getAnnotations(
+        CircleCenterMeasurementTool.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} CircleCenter measurements for viewport: ${viewportId}`);
+      return true;
+    } catch (error) {
+      console.error(`[MeasurementToolManager] Error clearing CircleCenter measurements:`, error);
+      return false;
+    }
+  }
+
+  static clearCircleCenterMeasurementsForViewports(viewportIds: string[]): boolean[] {
+    return viewportIds.map((viewportId) => this.clearCircleCenterMeasurements(viewportId));
+  }
 }