import React, { useEffect, useRef } from 'react'; import * as cornerstone from '@cornerstonejs/core'; import type { Types } from '@cornerstonejs/core'; import * as cornerstoneTools from '@cornerstonejs/tools'; import { annotation, SplineROITool } from '@cornerstonejs/tools'; import { eventTarget } from '@cornerstonejs/core'; import { registerGlobalTools } from '@/utils/cornerstoneToolsSetup'; import { MeasurementToolManager } from '@/utils/measurementToolManager'; import TibialPlateauAngleTool from '@/components/measures/TibialPlateauAngleTool'; 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 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 MidlineMeasurementTool from '@/components/measures/MidlineMeasurementTool'; import FindMidpointMeasurementTool from '@/components/measures/FindMidpointMeasurementTool'; import VerticalTiltMeasurementTool from '@/components/measures/VerticalTiltMeasurementTool'; import HorizontalTiltMeasurementTool from '@/components/measures/HorizontalTiltMeasurementTool'; import DicomOverlayTool from '@/components/overlay/DicomOverlayTool'; import ImageViewerErrorBoundary from './ImageViewerErrorBoundary'; import { boolean } from 'zod'; import { EVENTS } from '@cornerstonejs/core'; import { useSelector } from 'react-redux'; import { selectOverlayEnabled } from '@/states/view/dicomOverlaySlice'; import PolygonLengthMeasurementTool from '@/components/measures/PolygonLengthMeasurementTool'; import PolylineLengthMeasurementTool from '@/components/measures/PolylineLengthMeasurementTool'; const { MagnifyTool, PanTool, WindowLevelTool, StackScrollTool, ZoomTool, LabelTool, LengthTool, AngleTool,//角度测量工具 ToolGroupManager, Enums: csToolsEnums, PlanarRotateTool, } = cornerstoneTools; const { MouseBindings } = csToolsEnums; // let toolGroup: cornerstoneTools.Types.IToolGroup; // let currentViewportId: string; /** * 增强错误信息,添加更多上下文 */ function enhanceError( error: unknown, context: { imageUrls: string[]; imageIndex: number; viewportId: string; } ): Error { const baseError = error instanceof Error ? error : new Error(String(error)); const { imageUrls, imageIndex, viewportId } = context; const contextInfo = { imageUrls, imageIndex, viewportId, timestamp: new Date().toISOString(), userAgent: navigator.userAgent }; // 创建增强的错误消息 const enhancedMessage = `[${context.viewportId}] 图像加载失败: ${baseError.message} 上下文信息: ${JSON.stringify(contextInfo, null, 2)}`; const enhancedError = new Error(enhancedMessage); enhancedError.name = `ImageLoadError`; enhancedError.stack = `${enhancedError.name}: ${enhancedMessage}\n原错误堆栈: ${baseError.stack}`; return enhancedError; } /** * 判断是否为严重错误,需要抛出给 Error Boundary */ function isCriticalError(error: Error): boolean { const message = error.message.toLowerCase(); // 以下类型的错误被认为是严重的,需要用户干预 const criticalErrors = [ '图像url列表为空', '图像索引无效', '401', // 认证失败 '403', // 权限不足 '404', // 文件不存在 '500', // 服务器错误 '认证失败', '权限不足', 'unauthorized', 'forbidden', 'not found', 'internal server error' ]; return criticalErrors.some(keyword => message.includes(keyword)); } function getToolgroupByViewportId(currentViewportId: string) { const toolGroup = ToolGroupManager.getToolGroup( `STACK_TOOL_GROUP_ID_${currentViewportId}` ); if (!toolGroup) { console.log('toolGroup not found'); throw new Error('Tool group not found'); } return toolGroup; } function overlayRedRectangle(currentViewportId: string) { const viewport = cornerstone.getEnabledElementByViewportId(currentViewportId) .viewport as cornerstone.StackViewport; const viewportElement = viewport.element; const annotations = cornerstoneTools.annotation.state.getAnnotations( 'LinearSplineROI', viewportElement ); if (!annotations || annotations.length === 0) { console.log('No ROI annotations found'); return; } const annotation = annotations[annotations.length - 1]; const points = annotation?.data?.handles?.points; // 创建一个覆盖 Canvas const canvas = document.createElement('canvas'); canvas.style.position = 'absolute'; canvas.width = viewportElement.clientWidth; canvas.height = viewportElement.clientHeight; viewportElement.firstChild?.appendChild(canvas); const ctx = canvas.getContext('2d'); if (!ctx) { throw new Error('Failed to get 2D context from canvas'); } ctx.fillStyle = 'rgba(0, 0, 0, 1)'; // 将 ROI 坐标转换为 Canvas 坐标 if (!points) { console.log('No points found in handles'); return; } // Convert all points to canvas coordinates const z = viewport.getCurrentImageIdIndex() || 0; const canvasPoints = points.map((point: Types.Point2 | Types.Point3) => { const point3D: Types.Point3 = point.length === 2 ? [point[0], point[1], z] : point; return viewport.worldToCanvas(point3D); }); // Log for debugging console.log('Canvas Points:', canvasPoints); // Draw the polygon ctx.beginPath(); ctx.rect(0, 0, canvas.width, canvas.height); // Full canvas for evenodd rule ctx.moveTo(canvasPoints[0][0], canvasPoints[0][1]); // Start at the first point for (let i = 1; i < canvasPoints.length; i++) { ctx.lineTo(canvasPoints[i][0], canvasPoints[i][1]); // Connect to subsequent points } ctx.closePath(); // Close the polygon ctx.fill('evenodd'); // Fill with red } function registerTools(viewportId, renderingEngineId) { // 确保全局工具已注册(只会执行一次) registerGlobalTools(); // 创建该 viewport 的工具组 const toolGroupId = `STACK_TOOL_GROUP_ID_${viewportId}`; const toolGroupTmp = ToolGroupManager.createToolGroup(toolGroupId); if (!toolGroupTmp) { console.error( `[registerTools] Failed to create tool group for viewport: ${viewportId}` ); return; } const toolGroup = toolGroupTmp; // 添加工具到工具组 toolGroup.addTool(MagnifyTool.toolName); toolGroup.addTool(PanTool.toolName); toolGroup.addTool(WindowLevelTool.toolName); toolGroup.addTool(StackScrollTool.toolName); toolGroup.addTool(ZoomTool.toolName); toolGroup.addTool(LabelTool.toolName); toolGroup.addTool(PlanarRotateTool.toolName); toolGroup.addTool(LengthTool.toolName); // 添加线段测量工具 toolGroup.addTool(AngleTool.toolName); // 添加角度测量工具 toolGroup.addTool(TibialPlateauAngleTool.toolName); // 添加胫骨平台夹角测量工具 toolGroup.addTool(DARAMeasurementTool.toolName); // 添加髋臼水平角测量工具 toolGroup.addTool(HipDIMeasurementTool.toolName); // 添加髋关节牵引指数测量工具 toolGroup.addTool(HipNHAAngleMeasurementTool.toolName); // 添加髋关节水平角测量工具 toolGroup.addTool(VHSMeasurementTool.toolName); // 添加心锥比测量工具 toolGroup.addTool(TPLOMeasurementTool.toolName); // 添加TPLO测量工具 toolGroup.addTool(TTAMeasurementTool.toolName); // 添加TTA测量工具 toolGroup.addTool(CBLOMeasurementTool.toolName); // 添加CBLO测量工具 toolGroup.addTool(HipCoverageMeasurementTool.toolName); // 添加股骨头覆盖率测量工具 toolGroup.addTool(HipDorsalCoverageTool.toolName); // 添加髋臼背覆盖测量工具 toolGroup.addTool(CircleCenterMeasurementTool.toolName); // 添加找圆心测量工具 toolGroup.addTool(MidlineMeasurementTool.toolName); // 添加找中线测量工具 toolGroup.addTool(FindMidpointMeasurementTool.toolName); // 添加找中点测量工具 toolGroup.addTool(VerticalTiltMeasurementTool.toolName); // 添加直线垂直倾斜度测量工具 toolGroup.addTool(HorizontalTiltMeasurementTool.toolName); // 添加直线水平倾斜度测量工具 toolGroup.addTool(DicomOverlayTool.toolName); // 添加DICOM四角信息显示工具 toolGroup.addTool(PolygonLengthMeasurementTool.toolName); // 添加多边形长度测量工具 toolGroup.addTool(PolylineLengthMeasurementTool.toolName); // 添加拆线长度测量工具 // 设置默认工具状态 setupDefaultToolStates(toolGroup); // 添加 viewport 到工具组 toolGroup.addViewport(viewportId, renderingEngineId); console.log(`[registerTools] Tools registered for viewport: ${viewportId}`); } /** * 设置默认工具状态 */ function setupDefaultToolStates(toolGroup: cornerstoneTools.Types.IToolGroup) { // 设置平移工具(中键) toolGroup.setToolActive(PanTool.toolName, { bindings: [ { mouseButton: MouseBindings.Auxiliary, // Middle Click }, ], }); // 设置缩放工具(右键) toolGroup.setToolActive(ZoomTool.toolName, { bindings: [ { mouseButton: MouseBindings.Secondary, // Right Click }, ], }); // 设置滚轮滚动工具 toolGroup.setToolActive(StackScrollTool.toolName, { bindings: [ { mouseButton: MouseBindings.Wheel, // Mouse Wheel }, ], }); // 其他工具默认为被动状态 toolGroup.setToolPassive(MagnifyTool.toolName); toolGroup.setToolPassive(WindowLevelTool.toolName); toolGroup.setToolPassive(LabelTool.toolName); toolGroup.setToolPassive(PlanarRotateTool.toolName); toolGroup.setToolPassive(LengthTool.toolName); toolGroup.setToolPassive(AngleTool.toolName); toolGroup.setToolPassive(TibialPlateauAngleTool.toolName); toolGroup.setToolPassive(DARAMeasurementTool.toolName); toolGroup.setToolPassive(HipDIMeasurementTool.toolName); toolGroup.setToolPassive(HipNHAAngleMeasurementTool.toolName); toolGroup.setToolPassive(VHSMeasurementTool.toolName); toolGroup.setToolPassive(TPLOMeasurementTool.toolName); toolGroup.setToolPassive(TTAMeasurementTool.toolName); toolGroup.setToolPassive(CBLOMeasurementTool.toolName); toolGroup.setToolPassive(HipCoverageMeasurementTool.toolName); toolGroup.setToolPassive(HipDorsalCoverageTool.toolName); toolGroup.setToolPassive(CircleCenterMeasurementTool.toolName); toolGroup.setToolPassive(MidlineMeasurementTool.toolName); toolGroup.setToolPassive(FindMidpointMeasurementTool.toolName); toolGroup.setToolPassive(VerticalTiltMeasurementTool.toolName); toolGroup.setToolPassive(HorizontalTiltMeasurementTool.toolName); toolGroup.setToolPassive(PolygonLengthMeasurementTool.toolName); toolGroup.setToolPassive(PolylineLengthMeasurementTool.toolName); } export function addLMark(currentViewportId: string): void { // Implement the logic to add an L mark console.log('Adding L Mark viewport id : ', currentViewportId); const toolGroup = getToolgroupByViewportId(currentViewportId); // currentViewportId = viewportId; toolGroup.setToolActive(LabelTool.toolName, { bindings: [ // { // mouseButton: MouseBindings.Primary, // Left Click // }, ], }); const position: Types.Point3 = [100, 100, 0]; // Example position const text = 'L'; // Predefined text LabelTool.hydrate(currentViewportId, position, text); toolGroup.setToolPassive(LabelTool.toolName, { removeAllBindings: true, }); // const enabledElement = cornerstone.getEnabledElementByViewportId(currentViewportId); // cursors.elementCursor.resetElementCursor(elementRef.current as HTMLDivElement); } export function addRLabel(viewportId) { const toolGroup = getToolgroupByViewportId(viewportId); toolGroup.setToolActive(LabelTool.toolName, { bindings: [], }); const element = document.getElementById(viewportId); const elementHeight = element ? element.getBoundingClientRect().height : 0; const position: Types.Point3 = [100, elementHeight / 2, 0]; // Example position const text = 'R'; // Predefined text LabelTool.hydrate(viewportId, position, text); toolGroup.setToolPassive(LabelTool.toolName, { removeAllBindings: true }); } export function adjustBrightnessAndContrast(currentViewportId: string) { const toolGroup = getToolgroupByViewportId(currentViewportId); const planar = toolGroup.getToolInstance(WindowLevelTool.toolName); // Reset rotation angle const isActive = planar.mode === csToolsEnums.ToolModes.Active; if (isActive) { toolGroup.setToolPassive(WindowLevelTool.toolName, { removeAllBindings: true, }); } else { toolGroup.setToolActive(WindowLevelTool.toolName, { bindings: [ { mouseButton: MouseBindings.Primary, // Left Click }, ], }); } } /** * 防抖工具函数 * @param func 要防抖的函数 * @param wait 等待时间(毫秒) * @returns 防抖后的函数 */ function debounce any>(func: T, wait: number): (...args: Parameters) => void { let timeout: NodeJS.Timeout | null = null; return (...args: Parameters) => { if (timeout) clearTimeout(timeout); timeout = setTimeout(() => func(...args), wait); }; } function containImage(viewport:cornerstone.StackViewport) { viewport.getRenderingEngine().resize(/* immediate */ true); console.log(`使用 resetCamera方式`) viewport.resetCamera({ resetPan: true, resetZoom: true }); // 重置 pan/zoom 到 contain viewport.render(); } /** * 保持宽高比的图像适应函数 * 确保图像完全显示在容器内,同时保持原始宽高比(可能会有黑边) * @param currentViewportId 当前视口ID */ export function fitImageWithAspectRatio(currentViewportId: string): void { try { console.log(`[fitImageWithAspectRatio] 重新适配大小`) const viewport = cornerstone.getEnabledElementByViewportId(currentViewportId) .viewport as cornerstone.StackViewport; if (!viewport) { console.warn(`[fitImageWithAspectRatio] Viewport not found: ${currentViewportId}`); return; } containImage(viewport) } catch (error) { console.error(`[fitImageWithAspectRatio] Error fitting image with aspect ratio:`, error); } } export function fitImageSize(currentViewportId: string) { // 使用新的保持宽高比的适应函数 fitImageWithAspectRatio(currentViewportId); } export function deleteSelectedMark(currentViewportId: string): void { const viewport = cornerstone.getEnabledElementByViewportId(currentViewportId).viewport; const allAnnotations = cornerstoneTools.annotation.state.getAllAnnotations(); // 删除所有 LabelTool 创建的标记(包括 L/R 标记、预定义标记、自定义标记、时间戳标记) for (const annotation of allAnnotations) { if (annotation.metadata?.toolName === LabelTool.toolName) { cornerstoneTools.annotation.state.removeAnnotation( annotation.annotationUID! ); } } viewport.render(); } export function addMark(currentViewportId: string): void { // Implement the logic to add a mask console.log('Adding Mask'); // Add the specific logic to add a mask here cornerstoneTools.addTool(SplineROITool); const toolGroup = getToolgroupByViewportId(currentViewportId); toolGroup.addTool(SplineROITool.toolName); toolGroup.addToolInstance('LinearSplineROI', SplineROITool.toolName, { spline: { type: SplineROITool.SplineTypes.Linear, }, }); toolGroup.setToolActive('LinearSplineROI', { bindings: [{ mouseButton: MouseBindings.Primary }], }); } /** * 添加自定义文本标记到图像 */ export function addCustomMark(currentViewportId: string, text: string): void { console.log(`Adding custom mark: ${text} to viewport: ${currentViewportId}`); const toolGroup = getToolgroupByViewportId(currentViewportId); // 激活标签工具 toolGroup.setToolActive(LabelTool.toolName, { bindings: [], }); // 获取视口元素和位置 const element = document.getElementById(currentViewportId); if (!element) { console.error(`Element not found for viewport: ${currentViewportId}`); return; } // 计算标记位置(图像中心位置) const elementHeight = element.getBoundingClientRect().height; const elementWidth = element.getBoundingClientRect().width; const position: Types.Point3 = [elementWidth / 2, elementHeight / 2, 0]; // 添加标签 LabelTool.hydrate(currentViewportId, position, text); // 恢复工具状态 toolGroup.setToolPassive(LabelTool.toolName, { removeAllBindings: true, }); console.log(`Custom mark "${text}" added successfully`); } export function remoteMask(currentViewportId: string): void { // 1. 获取所有 annotation const all = annotation.state.getAllAnnotations(); // 2. 过滤出 LinearSplineROI 产生的 const toRemove = all.filter( (a) => a.metadata?.toolName === 'LinearSplineROI' ); // 3. 逐条删掉 toRemove.forEach((a) => { if (a.annotationUID) { annotation.state.removeAnnotation(a.annotationUID); } }); const viewport = cornerstone.getEnabledElementByViewportId(currentViewportId) .viewport as cornerstone.StackViewport; viewport.render(); console.log('Deleting Digital Mask'); const viewportElement = viewport.element; const firstChild = viewportElement.firstChild; if (firstChild) { const canvasElements = Array.from(firstChild.childNodes).filter( (child): child is Element => child instanceof Element && child.tagName === 'CANVAS' ); canvasElements.slice(1).forEach((canvas) => { firstChild.removeChild(canvas); }); } } export function HorizontalFlip(currentViewportId: string): void { const viewport = cornerstone.getEnabledElementByViewportId(currentViewportId) .viewport as cornerstone.StackViewport; // 切换水平翻转状态 const { flipHorizontal } = viewport.getCamera(); viewport.setCamera({ flipHorizontal: !flipHorizontal }); console.log('Flipping Image Horizontally'); } export function VerticalFlip(currentViewportId: string): void { const viewport = cornerstone.getEnabledElementByViewportId(currentViewportId) .viewport as cornerstone.StackViewport; // 切换竖直翻转状态 const { flipVertical } = viewport.getCamera(); viewport.setCamera({ flipVertical: !flipVertical }); } export function RotateCounterclockwise90(currentViewportId: string): void { const viewport = cornerstone.getEnabledElementByViewportId(currentViewportId) .viewport as cornerstone.StackViewport; // 获取当前相机 const camera = viewport.getCamera(); // 计算新的旋转角度(当前角度 + 90度) const newRotation = (camera.rotation ?? 0) + 90; // 但计算viewUp向量时,我们应该使用90度的弧度,而不是新角度的弧度! const ninetyDegreesRadians = (90 * Math.PI) / 180; // 获取当前的viewUp向量 const currentViewUp = camera.viewUp || [0, 1, 0]; // 计算旋转后的viewUp向量(这才是正确的相对旋转) const newViewUp: [number, number, number] = [ currentViewUp[0] * Math.cos(ninetyDegreesRadians) - currentViewUp[1] * Math.sin(ninetyDegreesRadians), currentViewUp[0] * Math.sin(ninetyDegreesRadians) + currentViewUp[1] * Math.cos(ninetyDegreesRadians), 0, ]; // 设置新的相机参数 viewport.setCamera({ ...camera, viewUp: newViewUp, rotation: newRotation % 360, // 确保角度在0-359范围内 }); viewport.render(); console.log('Rotating Image Counterclockwise 90°'); } export function RotateClockwise90(currentViewportId: string): void { const viewport = cornerstone.getEnabledElementByViewportId(currentViewportId) .viewport as cornerstone.StackViewport; const camera = viewport.getCamera(); // 计算新的旋转角度(当前角度 + 90度) const newRotation = (camera.rotation ?? 0) - 90; // 但计算viewUp向量时,我们应该使用90度的弧度,而不是新角度的弧度! const ninetyDegreesRadians = (90 * Math.PI) / 180; // 获取当前的viewUp向量 const currentViewUp = camera.viewUp || [0, 1, 0]; // 计算旋转后的viewUp向量(这才是正确的相对旋转) const newViewUp: [number, number, number] = [ currentViewUp[0] * Math.cos(ninetyDegreesRadians) - currentViewUp[1] * Math.sin(ninetyDegreesRadians), currentViewUp[0] * Math.sin(ninetyDegreesRadians) + currentViewUp[1] * Math.cos(ninetyDegreesRadians), 0, ]; // 设置新的相机参数 viewport.setCamera({ ...camera, viewUp: newViewUp, rotation: newRotation % 360, // 确保角度在0-359范围内 }); viewport.render(); console.log('Rotating Image Clockwise 90°'); } export function ResetImage(currentViewportId: string): void { const viewport = cornerstone.getEnabledElementByViewportId(currentViewportId) .viewport as cornerstone.StackViewport; // Implement the logic to reset the image // Resets the viewport's camera viewport.resetCamera(); // Resets the viewport's properties viewport.resetProperties(); viewport.render(); console.log('Resetting Image'); } export function InvertImage(currentViewportId: string): void { const viewport = cornerstone.getEnabledElementByViewportId(currentViewportId) .viewport as cornerstone.StackViewport; // Implement the logic to invert the image const invert = !viewport.getProperties().invert; viewport.setProperties({ invert }); viewport.render(); console.log('Inverting Image'); } export function setOriginalSize(currentViewportId: string) { const viewport = cornerstone.getEnabledElementByViewportId(currentViewportId) .viewport as cornerstone.StackViewport; // 1) 先正常 fit(或本来就是 fit 状态) viewport.resetCamera(); // 2) 计算“fit → 1:1”的放大倍数 const { dimensions, spacing } = viewport.getImageData(); console.log(`dimensions:${dimensions}, spacing:${spacing}`); const canvas = viewport.canvas; // 水平方向 1:1 需要的倍率 const cssPixelsPerDicomPx = canvas.clientWidth / (dimensions[0] * spacing[0]); // 垂直方向 1:1 需要的倍率 const cssPixelsPerDicomPy = canvas.clientHeight / (dimensions[1] * spacing[1]); // 取两者最小值,保证整张图不会被裁剪 const zoomFactor = Math.min(cssPixelsPerDicomPx, cssPixelsPerDicomPy); console.log(`zoomFactor:${zoomFactor}`); console.log( `canvas.clientWidth:${canvas.clientWidth}, dimensions[0]:${dimensions[0]}, spacing[0]:${spacing[0]}` ); console.log( `canvas.clientHeight:${canvas.clientHeight}, dimensions[1]:${dimensions[1]}, spacing[1]:${spacing[1]}` ); // 3) 直接放大 const zoom = viewport.getZoom(); viewport.setZoom((zoom * 1) / zoomFactor); viewport.render(); } export function activateMagnifier(currentViewportId: string) { console.log('Activating Magnifier'); const toolGroup = getToolgroupByViewportId(currentViewportId); const isActive = toolGroup.getActivePrimaryMouseButtonTool() === MagnifyTool.toolName; if (isActive) { toolGroup.setToolPassive(MagnifyTool.toolName, { removeAllBindings: true, }); } else { toolGroup.setToolActive(MagnifyTool.toolName, { bindings: [ { mouseButton: MouseBindings.Primary, // Left Click }, ], }); } } export function invertContrast(currentViewportId: string) { const viewport = cornerstone.getEnabledElementByViewportId(currentViewportId).viewport; const targetBool = !viewport.getProperties().invert; viewport.setProperties({ invert: targetBool, }); viewport.render(); } export function rotateAnyAngle(currentViewportId: string) { const toolGroup = getToolgroupByViewportId(currentViewportId); const planar = toolGroup.getToolInstance(PlanarRotateTool.toolName); // Reset rotation angle const isActive = planar.mode === csToolsEnums.ToolModes.Active; console.log( `PlanarRotateTool is currently ${isActive ? 'active' : 'inactive'}` ); if (isActive) { toolGroup.setToolPassive(PlanarRotateTool.toolName, { removeAllBindings: true, }); } else { toolGroup.setToolActive(PlanarRotateTool.toolName, { bindings: [ { mouseButton: MouseBindings.Primary, // Left Click }, ], }); } console.log('Rotating Image by Any Angle'); } // ==================== 线段测量相关函数 ==================== /** * 激活线段测量工具 */ export function activateLengthMeasurement(viewportId: string): boolean { console.log( `[activateLengthMeasurement] Activating length measurement for viewport: ${viewportId}` ); return MeasurementToolManager.activateLengthTool(viewportId); } /** * 停用线段测量工具 */ export function deactivateLengthMeasurement(viewportId: string): boolean { console.log( `[deactivateLengthMeasurement] Deactivating length measurement for viewport: ${viewportId}` ); return MeasurementToolManager.deactivateLengthTool(viewportId); } /** * 切换线段测量工具状态 */ export function toggleLengthMeasurement(viewportId: string): boolean { console.log( `[toggleLengthMeasurement] Toggling length measurement for viewport: ${viewportId}` ); return MeasurementToolManager.toggleLengthTool(viewportId); } /** * 清除线段测量标注 */ export function clearLengthMeasurements(viewportId: string): boolean { console.log( `[clearLengthMeasurements] Clearing length measurements for viewport: ${viewportId}` ); return MeasurementToolManager.clearLengthMeasurements(viewportId); } /** * 获取线段测量结果 */ // eslint-disable-next-line export function getLengthMeasurements(viewportId: string): any[] { console.log( `[getLengthMeasurements] Getting length measurements for viewport: ${viewportId}` ); return MeasurementToolManager.getLengthMeasurements(viewportId); } /** * 检查线段测量工具是否激活 */ 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); } // ==================== 胫骨平台夹角测量相关函数 ==================== /** * 激活胫骨平台夹角测量工具 */ export function activateTibialPlateauAngleMeasurement(viewportId: string): boolean { console.log( `[activateTibialPlateauAngleMeasurement] Activating TPA measurement for viewport: ${viewportId}` ); return MeasurementToolManager.activateTibialPlateauAngleTool(viewportId); } /** * 停用胫骨平台夹角测量工具 */ export function deactivateTibialPlateauAngleMeasurement(viewportId: string): boolean { console.log( `[deactivateTibialPlateauAngleMeasurement] Deactivating TPA measurement for viewport: ${viewportId}` ); return MeasurementToolManager.deactivateTibialPlateauAngleTool(viewportId); } /** * 切换胫骨平台夹角测量工具状态 */ export function toggleTibialPlateauAngleMeasurement(viewportId: string): boolean { console.log( `[toggleTibialPlateauAngleMeasurement] Toggling TPA measurement for viewport: ${viewportId}` ); return MeasurementToolManager.toggleTibialPlateauAngleTool(viewportId); } /** * 清除胫骨平台夹角测量标注 */ export function clearTibialPlateauAngleMeasurements(viewportId: string): boolean { console.log( `[clearTibialPlateauAngleMeasurements] Clearing TPA measurements for viewport: ${viewportId}` ); return MeasurementToolManager.clearTibialPlateauAngleMeasurements(viewportId); } /** * 获取胫骨平台夹角测量结果 */ // eslint-disable-next-line export function getTibialPlateauAngleMeasurements(viewportId: string): any[] { console.log( `[getTibialPlateauAngleMeasurements] Getting TPA measurements for viewport: ${viewportId}` ); return MeasurementToolManager.getTibialPlateauAngleMeasurements(viewportId); } /** * 检查胫骨平台夹角测量工具是否激活 */ export function isTibialPlateauAngleMeasurementActive(viewportId: string): boolean { return MeasurementToolManager.isTibialPlateauAngleToolActive(viewportId); } // ==================== 髋臼水平角测量相关函数 ==================== /** * 激活髋臼水平角测量工具 */ export function activateDARAMeasurement(viewportId: string): boolean { console.log( `[activateDARAMeasurement] Activating DARA measurement for viewport: ${viewportId}` ); return MeasurementToolManager.activateDARAMeasurementTool(viewportId); } /** * 停用髋臼水平角测量工具 */ export function deactivateDARAMeasurement(viewportId: string): boolean { console.log( `[deactivateDARAMeasurement] Deactivating DARA measurement for viewport: ${viewportId}` ); return MeasurementToolManager.deactivateDARAMeasurementTool(viewportId); } /** * 切换髋臼水平角测量工具状态 */ export function toggleDARAMeasurement(viewportId: string): boolean { console.log( `[toggleDARAMeasurement] Toggling DARA measurement for viewport: ${viewportId}` ); return MeasurementToolManager.toggleDARAMeasurementTool(viewportId); } /** * 清除髋臼水平角测量标注 */ export function clearDARAMeasurements(viewportId: string): boolean { console.log( `[clearDARAMeasurements] Clearing DARA measurements for viewport: ${viewportId}` ); return MeasurementToolManager.clearDARAMeasurements(viewportId); } /** * 获取髋臼水平角测量结果 */ // eslint-disable-next-line export function getDARAMeasurements(viewportId: string): any[] { console.log( `[getDARAMeasurements] Getting DARA measurements for viewport: ${viewportId}` ); return MeasurementToolManager.getDARAMeasurements(viewportId); } /** * 检查髋臼水平角测量工具是否激活 */ export function isDARAMeasurementActive(viewportId: string): boolean { return MeasurementToolManager.isDARAMeasurementToolActive(viewportId); } // ==================== 髋关节牵引指数测量相关函数 ==================== /** * 激活髋关节牵引指数测量工具 */ export function activateHipDIMeasurement(viewportId: string): boolean { console.log( `[activateHipDIMeasurement] Activating HipDI measurement for viewport: ${viewportId}` ); return MeasurementToolManager.activateHipDIMeasurementTool(viewportId); } /** * 停用髋关节牵引指数测量工具 */ export function deactivateHipDIMeasurement(viewportId: string): boolean { console.log( `[deactivateHipDIMeasurement] Deactivating HipDI measurement for viewport: ${viewportId}` ); return MeasurementToolManager.deactivateHipDIMeasurementTool(viewportId); } /** * 切换髋关节牵引指数测量工具状态 */ export function toggleHipDIMeasurement(viewportId: string): boolean { console.log( `[toggleHipDIMeasurement] Toggling HipDI measurement for viewport: ${viewportId}` ); return MeasurementToolManager.toggleHipDIMeasurementTool(viewportId); } /** * 清除髋关节牵引指数测量标注 */ export function clearHipDIMeasurements(viewportId: string): boolean { console.log( `[clearHipDIMeasurements] Clearing HipDI measurements for viewport: ${viewportId}` ); return MeasurementToolManager.clearHipDIMeasurements(viewportId); } /** * 获取髋关节牵引指数测量结果 */ // eslint-disable-next-line export function getHipDIMeasurements(viewportId: string): any[] { console.log( `[getHipDIMeasurements] Getting HipDI measurements for viewport: ${viewportId}` ); return MeasurementToolManager.getHipDIMeasurements(viewportId); } /** * 检查髋关节牵引指数测量工具是否激活 */ export function isHipDIMeasurementActive(viewportId: string): boolean { return MeasurementToolManager.isHipDIMeasurementToolActive(viewportId); } // ==================== 髋关节水平角测量相关函数 ==================== /** * 激活髋关节水平角测量工具 */ export function activateHipNHAAngleMeasurement(viewportId: string): boolean { console.log( `[activateHipNHAAngleMeasurement] Activating HipNHA measurement for viewport: ${viewportId}` ); return MeasurementToolManager.activateHipNHAAngleMeasurementTool(viewportId); } /** * 停用髋关节水平角测量工具 */ export function deactivateHipNHAAngleMeasurement(viewportId: string): boolean { console.log( `[deactivateHipNHAAngleMeasurement] Deactivating HipNHA measurement for viewport: ${viewportId}` ); return MeasurementToolManager.deactivateHipNHAAngleMeasurementTool(viewportId); } /** * 切换髋关节水平角测量工具状态 */ export function toggleHipNHAAngleMeasurement(viewportId: string): boolean { console.log( `[toggleHipNHAAngleMeasurement] Toggling HipNHA measurement for viewport: ${viewportId}` ); return MeasurementToolManager.toggleHipNHAAngleMeasurementTool(viewportId); } /** * 清除髋关节水平角测量标注 */ export function clearHipNHAAngleMeasurements(viewportId: string): boolean { console.log( `[clearHipNHAAngleMeasurements] Clearing HipNHA measurements for viewport: ${viewportId}` ); return MeasurementToolManager.clearHipNHAAngleMeasurements(viewportId); } /** * 获取髋关节水平角测量结果 */ // eslint-disable-next-line export function getHipNHAAngleMeasurements(viewportId: string): any[] { console.log( `[getHipNHAAngleMeasurements] Getting HipNHA measurements for viewport: ${viewportId}` ); return MeasurementToolManager.getHipNHAAngleMeasurements(viewportId); } /** * 检查髋关节水平角测量工具是否激活 */ 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); } // ==================== TTA测量相关函数 ==================== /** * 激活TTA测量工具 */ export function activateTTAMeasurement(viewportId: string): boolean { console.log( `[activateTTAMeasurement] Activating TTA measurement for viewport: ${viewportId}` ); return MeasurementToolManager.activateTTAMeasurementTool(viewportId); } /** * 停用TTA测量工具 */ export function deactivateTTAMeasurement(viewportId: string): boolean { console.log( `[deactivateTTAMeasurement] Deactivating TTA measurement for viewport: ${viewportId}` ); return MeasurementToolManager.deactivateTTAMeasurementTool(viewportId); } /** * 切换TTA测量工具状态 */ export function toggleTTAMeasurement(viewportId: string): boolean { console.log( `[toggleTTAMeasurement] Toggling TTA measurement for viewport: ${viewportId}` ); return MeasurementToolManager.toggleTTAMeasurementTool(viewportId); } /** * 清除TTA测量标注 */ export function clearTTAMeasurements(viewportId: string): boolean { console.log( `[clearTTAMeasurements] Clearing TTA measurements for viewport: ${viewportId}` ); return MeasurementToolManager.clearTTAMeasurements(viewportId); } /** * 获取TTA测量结果 */ // eslint-disable-next-line export function getTTAMeasurements(viewportId: string): any[] { console.log( `[getTTAMeasurements] Getting TTA measurements for viewport: ${viewportId}` ); return MeasurementToolManager.getTTAMeasurements(viewportId); } /** * 检查TTA测量工具是否激活 */ export function isTTAMeasurementActive(viewportId: string): boolean { return MeasurementToolManager.isTTAMeasurementToolActive(viewportId); } // ==================== CBLO测量相关函数 ==================== export function activateCBLOMeasurement(viewportId: string): boolean { console.log(`[activateCBLOMeasurement] Activating CBLO measurement for viewport: ${viewportId}`); return MeasurementToolManager.activateCBLOMeasurementTool(viewportId); } export function deactivateCBLOMeasurement(viewportId: string): boolean { console.log(`[deactivateCBLOMeasurement] Deactivating CBLO measurement for viewport: ${viewportId}`); return MeasurementToolManager.deactivateCBLOMeasurementTool(viewportId); } export function clearCBLOMeasurements(viewportId: string): boolean { console.log(`[clearCBLOMeasurements] Clearing CBLO measurements for viewport: ${viewportId}`); return MeasurementToolManager.clearCBLOMeasurements(viewportId); } // ==================== HipCoverage测量相关函数 ==================== export function activateHipCoverageMeasurement(viewportId: string): boolean { console.log(`[activateHipCoverageMeasurement] Activating HipCoverage measurement for viewport: ${viewportId}`); return MeasurementToolManager.activateHipCoverageMeasurementTool(viewportId); } export function deactivateHipCoverageMeasurement(viewportId: string): boolean { console.log(`[deactivateHipCoverageMeasurement] Deactivating HipCoverage measurement for viewport: ${viewportId}`); return MeasurementToolManager.deactivateHipCoverageMeasurementTool(viewportId); } export function clearHipCoverageMeasurements(viewportId: string): boolean { console.log(`[clearHipCoverageMeasurements] Clearing HipCoverage measurements for viewport: ${viewportId}`); return MeasurementToolManager.clearHipCoverageMeasurements(viewportId); } // ==================== HipDorsalCoverage测量相关函数 ==================== export function activateHipDorsalCoverageMeasurement(viewportId: string): boolean { console.log(`[activateHipDorsalCoverageMeasurement] Activating HipDorsalCoverage measurement for viewport: ${viewportId}`); return MeasurementToolManager.activateHipDorsalCoverageTool(viewportId); } export function deactivateHipDorsalCoverageMeasurement(viewportId: string): boolean { console.log(`[deactivateHipDorsalCoverageMeasurement] Deactivating HipDorsalCoverage measurement for viewport: ${viewportId}`); return MeasurementToolManager.deactivateHipDorsalCoverageTool(viewportId); } 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 function activateMidlineMeasurement(viewportId: string): boolean { console.log(`[activateMidlineMeasurement] Activating Midline measurement for viewport: ${viewportId}`); return MeasurementToolManager.activateMidlineMeasurementTool(viewportId); } export function deactivateMidlineMeasurement(viewportId: string): boolean { console.log(`[deactivateMidlineMeasurement] Deactivating Midline measurement for viewport: ${viewportId}`); return MeasurementToolManager.deactivateMidlineMeasurementTool(viewportId); } export function clearMidlineMeasurements(viewportId: string): boolean { console.log(`[clearMidlineMeasurements] Clearing Midline measurements for viewport: ${viewportId}`); return MeasurementToolManager.clearMidlineMeasurements(viewportId); } // ==================== 找中点测量相关函数 ==================== export function activateFindMidpointMeasurement(viewportId: string): boolean { console.log(`[activateFindMidpointMeasurement] Activating FindMidpoint measurement for viewport: ${viewportId}`); return MeasurementToolManager.activateFindMidpointMeasurementTool(viewportId); } export function deactivateFindMidpointMeasurement(viewportId: string): boolean { console.log(`[deactivateFindMidpointMeasurement] Deactivating FindMidpoint measurement for viewport: ${viewportId}`); return MeasurementToolManager.deactivateFindMidpointMeasurementTool(viewportId); } export function clearFindMidpointMeasurements(viewportId: string): boolean { console.log(`[clearFindMidpointMeasurements] Clearing FindMidpoint measurements for viewport: ${viewportId}`); return MeasurementToolManager.clearFindMidpointMeasurements(viewportId); } // ==================== 直线垂直倾斜度测量相关函数 ==================== export function activateVerticalTiltMeasurement(viewportId: string): boolean { console.log(`[activateVerticalTiltMeasurement] Activating VerticalTilt measurement for viewport: ${viewportId}`); return MeasurementToolManager.activateVerticalTiltMeasurementTool(viewportId); } export function deactivateVerticalTiltMeasurement(viewportId: string): boolean { console.log(`[deactivateVerticalTiltMeasurement] Deactivating VerticalTilt measurement for viewport: ${viewportId}`); return MeasurementToolManager.deactivateVerticalTiltMeasurementTool(viewportId); } export function clearVerticalTiltMeasurements(viewportId: string): boolean { console.log(`[clearVerticalTiltMeasurements] Clearing VerticalTilt measurements for viewport: ${viewportId}`); return MeasurementToolManager.clearVerticalTiltMeasurements(viewportId); } // ==================== 直线水平倾斜度测量相关函数 ==================== export function activateHorizontalTiltMeasurement(viewportId: string): boolean { console.log(`[activateHorizontalTiltMeasurement] Activating HorizontalTilt measurement for viewport: ${viewportId}`); return MeasurementToolManager.activateHorizontalTiltMeasurementTool(viewportId); } export function deactivateHorizontalTiltMeasurement(viewportId: string): boolean { console.log(`[deactivateHorizontalTiltMeasurement] Deactivating HorizontalTilt measurement for viewport: ${viewportId}`); return MeasurementToolManager.deactivateHorizontalTiltMeasurementTool(viewportId); } export function clearHorizontalTiltMeasurements(viewportId: string): boolean { console.log(`[clearHorizontalTiltMeasurements] Clearing HorizontalTilt measurements for viewport: ${viewportId}`); return MeasurementToolManager.clearHorizontalTiltMeasurements(viewportId); } // ==================== 多边形长度测量相关函数 ==================== /** * 激活多边形长度测量工具 */ export function activatePolygonLengthMeasurement(viewportId: string): boolean { console.log(`[activatePolygonLengthMeasurement] Activating PolygonLength measurement for viewport: ${viewportId}`); return MeasurementToolManager.activatePolygonLengthMeasurementTool(viewportId); } /** * 停用多边形长度测量工具 */ export function deactivatePolygonLengthMeasurement(viewportId: string): boolean { console.log(`[deactivatePolygonLengthMeasurement] Deactivating PolygonLength measurement for viewport: ${viewportId}`); return MeasurementToolManager.deactivatePolygonLengthMeasurementTool(viewportId); } /** * 清除多边形长度测量标注 */ export function clearPolygonLengthMeasurements(viewportId: string): boolean { console.log(`[clearPolygonLengthMeasurements] Clearing PolygonLength measurements for viewport: ${viewportId}`); return MeasurementToolManager.clearPolygonLengthMeasurements(viewportId); } // ==================== 拆线长度测量相关函数 ==================== /** * 激活拆线长度测量工具 */ export function activatePolylineLengthMeasurement(viewportId: string): boolean { console.log(`[activatePolylineLengthMeasurement] Activating PolylineLength measurement for viewport: ${viewportId}`); return MeasurementToolManager.activatePolylineLengthMeasurementTool(viewportId); } /** * 停用拆线长度测量工具 */ export function deactivatePolylineLengthMeasurement(viewportId: string): boolean { console.log(`[deactivatePolylineLengthMeasurement] Deactivating PolylineLength measurement for viewport: ${viewportId}`); return MeasurementToolManager.deactivatePolylineLengthMeasurementTool(viewportId); } /** * 清除拆线长度测量标注 */ export function clearPolylineLengthMeasurements(viewportId: string): boolean { console.log(`[clearPolylineLengthMeasurements] Clearing PolylineLength measurements for viewport: ${viewportId}`); return MeasurementToolManager.clearPolylineLengthMeasurements(viewportId); } export class ImageLoadError extends Error { constructor(message: string, failedImageIds: string[]) { super(message); this.name = 'ImageLoadError'; this.failedImageIds = failedImageIds; // 附加失败 ID 列表 } failedImageIds: string[]; } /** * 安全的图像栈设置函数,带超时保护和错误捕获 * @param viewport - Cornerstone viewport 实例 * @param imageIds - 图像 ID 数组 * @param imageIndex - 当前图像索引 * @param timeout - 超时时间(毫秒),默认30秒 */ export async function safeSetStack( viewport: any, imageIds: string[], imageIndex: number, timeout: number = 30000 ): Promise { const errors: string[] = []; // 错误事件处理器 const handler = (evt: any) => { console.error(`捕获到图像加载错误: ${evt.detail.imageId}`); errors.push(evt.detail.imageId); }; // 添加错误监听器 eventTarget.addEventListener(EVENTS.IMAGE_LOAD_ERROR, handler); try { // 创建超时 Promise const timeoutPromise = new Promise((_, reject) => { setTimeout(() => { reject(new Error(`图像加载超时 (${timeout}ms)`)); }, timeout); }); // 等待 setStack 完成或超时 await Promise.race([ viewport.setStack(imageIds, imageIndex), timeoutPromise ]); // setStack 完成后,检查是否有错误 if (errors.length > 0) { throw new ImageLoadError( `图像加载失败:共 ${errors.length} 张(${errors.join(', ')})`, errors ); } console.log(`✅ 图像栈设置成功: ${imageIds.length} 张图像`); } finally { // 无论成功失败,都移除监听器(清理资源) eventTarget.removeEventListener(EVENTS.IMAGE_LOAD_ERROR, handler); } } const StackViewer = ({ imageIndex = 0, imageUrls = [], viewportId, renderingEngineId, selected }: { imageIndex?: number; imageUrls?: string[]; viewportId: string; renderingEngineId: string; selected?: boolean; }) => { const elementRef = useRef(null); // 用于捕获异步错误并在渲染时抛出,让 Error Boundary 能够捕获 const [renderError, setRenderError] = React.useState(null); // 获取overlay启用状态 const overlayEnabled = useSelector(selectOverlayEnabled); // const action = useSelector((state: RootState) => state.functionArea.action); // const dispatch = useDispatch(); // 监听overlay状态变化,启用/禁用DicomOverlayTool useEffect(() => { try { const toolGroup = MeasurementToolManager.getToolGroup(viewportId); // const toolGroup = ToolGroupManager.getToolGroup(`STACK_TOOL_GROUP_ID_${viewportId}`); if (!toolGroup) { console.warn(`[StackViewer] Tool group not found for viewport: ${viewportId}`); return; } if (overlayEnabled) { console.log(`[StackViewer] Enabling DicomOverlay for viewport: ${viewportId}`); toolGroup.setToolEnabled(DicomOverlayTool.toolName); } else { console.log(`[StackViewer] Disabling DicomOverlay for viewport: ${viewportId}`); toolGroup.setToolDisabled(DicomOverlayTool.toolName); //再次启用,表示关闭四角信息 toolGroup.setToolEnabled(DicomOverlayTool.toolName); } } catch (error) { console.error(`[StackViewer] Error toggling DicomOverlay:`, error); } }, [overlayEnabled, viewportId]); // // 监听imageUrls变化并重新加载图像 // useEffect(() => { // const renderingEngine = cornerstone.getRenderingEngine(renderingEngineId); // if (!renderingEngine) { // return; // } // const viewport = renderingEngine.getViewport(viewportId) as cornerstone.Types.IStackViewport; // if (viewport && imageUrls.length > 0) { // console.log(`[StackViewer] imageUrls changed for viewport ${viewportId}, reloading...`); // viewport.setStack(imageUrls, imageIndex).then(() => { // viewport.render(); // }).catch((error) => { // console.error('[StackViewer] Error reloading image stack:', error); // }); // } // }, [imageUrls, imageIndex, viewportId, renderingEngineId]); useEffect(() => { const setup = async () => { // 清除之前的错误状态(如果有) setRenderError(null); // // 初始化 Cornerstone // cornerstone.init(); // cornerstoneTools.init(); // const state = store.getState(); // console.log(`当前系统模式:${state.systemMode.mode}`); // const token = // state.systemMode.mode === SystemMode.Emergency // ? state.product.guest // : state.userInfo.token; // console.log(`token stack.image.viewer: ${token}`); // cornerstoneDICOMImageLoader.init({ // maxWebWorkers: navigator.hardwareConcurrency || 1, // errorInterceptor: (error) => { // if (error.status === 401) { // console.error('Authentication failed. Please refresh the token.'); // } // console.error(`请求dcm文件出错:${error}`); // }, // beforeSend: (xhr, imageId, defaultHeaders) => { // return { // ...defaultHeaders, // Authorization: `Bearer ${token}`, // Language: 'en', // Product: 'DROS', // Source: 'Electron', // }; // }, // }); // const currentViewportId = viewportId; eventTarget.addEventListener( cornerstoneTools.Enums.Events.ANNOTATION_COMPLETED, (evt) => { const { annotation } = evt.detail; console.log('Annotation completed event:', annotation); if (annotation.metadata.toolName === 'LinearSplineROI') { console.log('SplineROITool annotation completed:', annotation); overlayRedRectangle(viewportId); //取消工具激活状态 const toolGroup = ToolGroupManager.getToolGroup( 'STACK_TOOL_GROUP_ID' ); if (!toolGroup) { console.log('toolGroup not found'); } toolGroup?.setToolPassive('LinearSplineROI', { removeAllBindings: true, }); } } ); const viewportInput: cornerstone.Types.PublicViewportInput = { viewportId, element: elementRef.current!, type: cornerstone.Enums.ViewportType.STACK, }; const renderingEngine = cornerstone.getRenderingEngine(renderingEngineId); if (!renderingEngine) { console.error( `[stack.image.viewer] No rendering engine with id ${renderingEngineId} found` ); return; } // Enable the element for use with Cornerstone renderingEngine.enableElement(viewportInput); registerTools(viewportId, renderingEngine.id); // Get the stack viewport that was created const viewport = renderingEngine.getViewport( viewportId ) as cornerstone.Types.IStackViewport; // 增强的图像加载逻辑,包含完善的错误处理 try { console.log(`重新加载图像----开始`); console.log(`图像URLs:`, imageUrls); console.log(`图像索引:`, imageIndex); // 1. 验证图像URLs if (!imageUrls || imageUrls.length === 0) { throw new Error('图像URL列表为空'); } // 2. 验证图像索引 if (imageIndex < 0 || imageIndex >= imageUrls.length) { throw new Error(`图像索引无效: ${imageIndex}, 有效范围: 0-${imageUrls.length - 1}`); } // 3. 预检查图像可访问性(可选) // await validateImageUrls(imageUrls); // 4. 设置图像栈,包含超时保护 await safeSetStack(viewport, imageUrls, imageIndex); // 5. 图像加载完成后,自动应用保持宽高比的适应 fitImageWithAspectRatio(viewportId); console.log(`重新加载图像----结束`); } catch (error) { // 不直接抛出错误,而是保存到状态中 // 在下次渲染时抛出,这样 Error Boundary 才能捕获 const enhancedError = enhanceError(error, { imageUrls, imageIndex, viewportId }); setRenderError(enhancedError); return; // 不再继续执行后续代码 // 根据错误严重程度决定处理策略 if (isCriticalError(enhancedError)) { // 严重错误:保存到状态,让 Error Boundary 处理 console.error('🚨 严重图像加载错误,将在渲染时抛出给 Error Boundary:', enhancedError); setRenderError(enhancedError); return; // 不再继续执行后续代码 } else { // 非严重错误:仅记录日志,继续运行 console.warn('⚠️ 图像加载警告:', enhancedError); // 可以在这里尝试降级策略,比如显示占位符 } } viewport.render(); }; setup(); }, [elementRef, imageIndex, viewportId, renderingEngineId, imageUrls[0]]); // 监听容器大小变化,自动保持宽高比 useEffect(() => { if (!elementRef.current) return; // 创建防抖的适应函数 const debouncedFit = debounce(() => { // console.log(`容器大小变化了---------`) requestAnimationFrame(() => { fitImageWithAspectRatio(viewportId); }); }, 100); // 创建ResizeObserver监听容器大小变化 const resizeObserver = new ResizeObserver(debouncedFit); resizeObserver.observe(elementRef.current); console.log(`[StackViewer] ResizeObserver attached to viewport: ${viewportId}`); // 清理函数 return () => { resizeObserver.disconnect(); console.log(`[StackViewer] ResizeObserver disconnected from viewport: ${viewportId}`); }; }, [viewportId]); // 在渲染期间检查错误状态,如果有错误则抛出 // 这样 Error Boundary 就能捕获到异步错误了 if (renderError) { throw renderError; } return (
e.preventDefault()} style={{ width: '100%', height: '100%', backgroundColor: '#000', border: selected ? '2px solid blue' : '1px solid gray', }} /> ); }; /** * 带错误边界的 StackViewer 包装组件 * 自动处理图像加载错误,提供优雅的错误恢复机制 */ export const StackViewerWithErrorBoundary = ({ imageIndex = 0, imageUrls = [], viewportId, renderingEngineId, selected, maxRetries = 3, onError }: { imageIndex?: number; imageUrls?: string[]; viewportId: string; renderingEngineId: string; selected?: boolean; maxRetries?: number; onError?: (error: Error, errorInfo: any) => void; }) => { return ( ); }; export default StackViewer;