| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138 |
- 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 LineGrayscaleMeasurementTool from '@/components/measures/LineGrayscaleMeasurementTool';
- import RectangleGrayscaleMeasurementTool from '@/components/measures/RectangleGrayscaleMeasurementTool';
- import MaskTool from '@/components/tools/MaskTool';
- import DicomOverlayTool from '@/components/overlay/DicomOverlayTool';
- import ImageViewerErrorBoundary from './ImageViewerErrorBoundary';
- import { boolean } from 'zod';
- import { EVENTS } from '@cornerstonejs/core';
- import { useSelector, useDispatch } from 'react-redux';
- import { selectOverlayEnabled } from '@/states/view/dicomOverlaySlice';
- import { initPlayback, setFrameDetectionResult } from '@/states/view/playbackSlice';
- import PolygonLengthMeasurementTool from '@/components/measures/PolygonLengthMeasurementTool';
- import PolylineLengthMeasurementTool from '@/components/measures/PolylineLengthMeasurementTool';
- import { PlaybackController } from '@/pages/view/components/playback/PlaybackController';
- import { FloatingPlaybackControls } from '@/pages/view/components/playback/FloatingPlaybackControls';
- import { getIpPort } from '@/API/config';
- import { getRenderingEngine } from '@cornerstonejs/core';
- const {
- MagnifyTool,
- PanTool,
- WindowLevelTool,
- StackScrollTool,
- ZoomTool,
- LabelTool,
- LengthTool,
- AngleTool,//角度测量工具
- ToolGroupManager,
- Enums: csToolsEnums,
- PlanarRotateTool,
- } = cornerstoneTools;
- // 导入 cursors 用于光标管理
- import { cursors } from '@cornerstonejs/tools';
- import { message } from 'antd';
- const { MouseBindings } = csToolsEnums;
- // 全局工具状态变化监听器注册标志
- let cursorResetListenerRegistered = false;
- // 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}`;
- // 检查工具组是否已存在
- let toolGroup = ToolGroupManager.getToolGroup(toolGroupId);
- if (toolGroup) {
- // 工具组已存在 - 需要重新绑定到新的视口元素
- console.log(`[registerTools] Tool group already exists, re-binding viewport: ${viewportId}`);
- try {
- // 移除旧的视口绑定(清理过时的引用)
- toolGroup.removeViewports(renderingEngineId, viewportId);
- console.log(`[registerTools] Removed old viewport binding`);
- } catch (error) {
- console.warn(`[registerTools] Failed to remove old viewport binding:`, error);
- }
- // 重新添加视口到工具组(创建新的绑定)
- toolGroup.addViewport(viewportId, renderingEngineId);
- console.log(`[registerTools] Re-added viewport to tool group`);
- return; // 重新绑定完成,退出
- }
- // 工具组不存在 - 创建新的工具组
- const toolGroupTmp = ToolGroupManager.createToolGroup(toolGroupId);
- if (!toolGroupTmp) {
- console.error(
- `[registerTools] Failed to create tool group for viewport: ${viewportId}`
- );
- return;
- }
- 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); // 添加拆线长度测量工具
- toolGroup.addTool(LineGrayscaleMeasurementTool.toolName); // 添加直线灰度测量工具
- toolGroup.addTool(RectangleGrayscaleMeasurementTool.toolName); // 添加矩形区域灰度测量工具
- toolGroup.addTool(MaskTool.toolName); // 添加掩码工具
- // 设置默认工具状态
- setupDefaultToolStates(toolGroup);
- // 添加 viewport 到工具组
- toolGroup.addViewport(viewportId, renderingEngineId);
- // 添加全局工具模式变化监听器,自动重置光标(当前项目中使用一对一映射)
- if (!cursorResetListenerRegistered) {
- eventTarget.addEventListener(
- cornerstoneTools.Enums.Events.TOOL_MODE_CHANGED,
- (event) => {
- // 检查是否有活动工具,当前一对一映射设计只关注全局活动工具状态
- const allToolGroups = ToolGroupManager.getAllToolGroups();
- let hasActiveTool = false;
- for (const toolGroup of allToolGroups) {
- if (toolGroup.getActivePrimaryMouseButtonTool()) {
- hasActiveTool = true;
- break;
- }
- }
- if (!hasActiveTool) {
- // 没有活动工具时,重置所有视口光标为默认(适用于当前一对一设计)
- for (const toolGroup of allToolGroups) {
- const viewportIds = toolGroup.getViewportIds();
- viewportIds.forEach(viewportId => {
- try {
- const enabledElement = cornerstone.getEnabledElementByViewportId(viewportId);
- if (enabledElement?.viewport.element) {
- enabledElement.viewport.element.style.cursor = 'default';
- }
- } catch (error) {
- // 忽略错误,可能视口已被销毁
- }
- });
- }
- }
- }
- );
- cursorResetListenerRegistered = true; // 防止重复注册
- }
- 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);
- toolGroup.setToolPassive(LineGrayscaleMeasurementTool.toolName);
- toolGroup.setToolPassive(RectangleGrayscaleMeasurementTool.toolName);
- toolGroup.setToolPassive(MaskTool.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(currentViewportId: string): void {
- console.log('Adding R Mark viewport id : ', currentViewportId);
- const toolGroup = getToolgroupByViewportId(currentViewportId);
- toolGroup.setToolActive(LabelTool.toolName, {
- bindings: [
- // {
- // mouseButton: MouseBindings.Primary, // Left Click
- // },
- ],
- });
- // R标记相对于L标记向右偏移一些距离
- const position: Types.Point3 = [300, 100, 0]; // 在L标记右侧200像素位置
- const text = 'R'; // Predefined text
- LabelTool.hydrate(currentViewportId, position, text);
- toolGroup.setToolPassive(LabelTool.toolName, {
- removeAllBindings: true,
- });
- }
- /**
- * 防抖工具函数
- * @param func 要防抖的函数
- * @param wait 等待时间(毫秒)
- * @returns 防抖后的函数
- */
- function debounce<T extends (...args: any[]) => any>(func: T, wait: number): (...args: Parameters<T>) => void {
- let timeout: NodeJS.Timeout | null = null;
- return (...args: Parameters<T>) => {
- 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 renderingEngine = getRenderingEngine("myRenderingEngine");
- if (renderingEngine === undefined) {
- console.error(`Rendering engine not found for viewport: ${currentViewportId}`);
- message.error(`添加注释失败`);
- return;
- }
- const viewport = renderingEngine.getViewport(currentViewportId);
- // 计算标记位置(图像中心位置)
- // 1️⃣ canvas 坐标(屏幕中心)
- const { width, height } = element.getBoundingClientRect();
- const canvasPoint: Types.Point2 = [width / 2, height / 2];
- // 2️⃣ 转为 world 坐标(关键!)
- const worldPoint = viewport.canvasToWorld(canvasPoint);
- // 3️⃣ 使用 world 坐标 添加标签
- LabelTool.hydrate(currentViewportId, worldPoint, 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];
- // 计算顺时针旋转90度的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;
- // 获取当前图像ID以访问原始元数据
- const currentImageId = viewport.getCurrentImageId();
- // 计算正确的原始invert值
- // 不能直接使用 viewport.getProperties().invert,因为"反色对比"功能会修改它
- // 需要从图像的原始元数据中获取 photometricInterpretation
- let correctOriginalInvert = false; // 默认值
- try {
- // 从Cornerstone缓存中获取图像对象
- const image = cornerstone.cache.getImage(currentImageId) as any;
- if (image) {
- // 方法1: 从图像的metadata中获取photometricInterpretation
- const photometricInterpretation =
- image.photometricInterpretation ||
- image.metadata?.dicomMetadata?.image?.photometricInterpretation ||
- image.imageFrame?.photometricInterpretation;
- // MONOCHROME1需要invert=true才能正确显示
- if (photometricInterpretation === 'MONOCHROME1') {
- correctOriginalInvert = true;
- }
- console.log('从图像元数据解析原始invert值', {
- imageId: currentImageId,
- photometricInterpretation,
- correctOriginalInvert
- });
- } else {
- console.warn('无法从缓存获取图像,使用默认invert值');
- }
- } catch (error) {
- console.warn('获取图像元数据失败,使用默认invert值', error);
- }
- // 重置相机设置(缩放、平移、旋转等)
- viewport.resetCamera();
- // 重置视口属性
- viewport.resetProperties();
- // ⚠️ 重要发现:resetProperties() 后需要使用反转的 invert 值
- //
- // 原因分析:
- // 1. 初始加载时:MONOCHROME1 图像使用 invert=true 正常显示
- // 2. resetProperties() 后:渲染管线状态改变,需要 invert=false 才能正常显示
- // 3. 这可能与 resetProperties() 重置了 VOI LUT 或其他影响渲染的属性有关
- //
- // 因此:
- // - MONOCHROME1 (correctOriginalInvert=true) → 设置 invert=false
- // - MONOCHROME2 (correctOriginalInvert=false) → 设置 invert=true
- //
- // 这个行为虽然反直觉,但经过实际验证是正确的
- viewport.setProperties({ invert: !correctOriginalInvert });
- viewport.render();
- console.log('Resetting Image', {
- imageId: currentImageId,
- restoredInvert: correctOriginalInvert
- });
- }
- 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) 先reset到fit状态
- viewport.resetCamera();
- // 2) 计算像素级1:1需要的zoom(完全忽略spacing!)
- const { dimensions } = viewport.getImageData();
- const canvas = viewport.canvas;
- // fit状态下,图像缩放到canvas的比例
- const fitScale = Math.min(
- canvas.clientWidth / dimensions[0],
- canvas.clientHeight / dimensions[1]
- );
- // 要达到像素级1:1,需要的zoom = 1 / fitScale
- const finalZoom = 1 / fitScale;
- console.log(`[setOriginalSize] Pixel-perfect 1:1 zoom calculation:`);
- console.log(` - Image dimensions: ${dimensions[0]}x${dimensions[1]}`);
- console.log(` - Canvas size: ${canvas.clientWidth}x${canvas.clientHeight}`);
- console.log(` - Fit scale: ${fitScale.toFixed(4)}`);
- console.log(` - Final zoom (1:1): ${finalZoom.toFixed(4)}`);
- viewport.setZoom(finalZoom);
- viewport.render();
- }
- /** 停用 Magnifier 工具
- */
- export function deactivateMagnifier(currentViewportId: string) {
- console.log('Deactivating Magnifier');
- const toolGroup = getToolgroupByViewportId(currentViewportId);
- toolGroup.setToolPassive(MagnifyTool.toolName, {
- removeAllBindings: true,
- });
- }
- /** 激活 Magnifier 工具
- */
- export function activateMagnifier(currentViewportId: string) {
- console.log('Activating Magnifier');
- const toolGroup = getToolgroupByViewportId(currentViewportId);
- toolGroup.setToolActive(MagnifyTool.toolName, {
- bindings: [
- {
- mouseButton: MouseBindings.Primary, // Left Click
- },
- ],
- });
- }
- /** 激活 WindowLevel 工具
- */
- export function activateWindowLevel(currentViewportId: string) {
- const toolGroup = getToolgroupByViewportId(currentViewportId);
- toolGroup.setToolActive(WindowLevelTool.toolName, {
- bindings: [
- {
- mouseButton: MouseBindings.Primary, // Left Click
- },
- ],
- });
- }
- /** 停用 WindowLevel 工具
- */
- export function deactivateWindowLevel(currentViewportId: string) {
- console.log('Deactivating WindowLevel Tool');
- const toolGroup = getToolgroupByViewportId(currentViewportId);
- toolGroup.setToolPassive(WindowLevelTool.toolName, {
- removeAllBindings: true,
- });
- }
- /**
- * 停用 Pan 工具
- */
- export function deactivatePan(currentViewportId: string) {
- console.log('Deactivating Pan Tool');
- const toolGroup = getToolgroupByViewportId(currentViewportId);
- toolGroup.setToolPassive(PanTool.toolName, {
- removeAllBindings: true,
- });
- }
- /**
- * 激活 Pan 工具
- */
- export function activatePan(currentViewportId: string) {
- console.log('Activating Pan Tool');
- const toolGroup = getToolgroupByViewportId(currentViewportId);
- toolGroup.setToolActive(PanTool.toolName, {
- bindings: [
- {
- mouseButton: MouseBindings.Primary, // Left Click
- },
- ],
- });
- }
- /** 停用 Zoom 工具
- */
- export function deactivateZoom(currentViewportId: string) {
- console.log('Deactivating Zoom Tool');
- const toolGroup = getToolgroupByViewportId(currentViewportId);
- toolGroup.setToolPassive(ZoomTool.toolName, {
- removeAllBindings: true,
- });
- }
- /**
- * 激活 Zoom 工具
- */
- export function activateZoom(currentViewportId: string) {
- console.log('Activating Zoom Tool');
- const toolGroup = getToolgroupByViewportId(currentViewportId);
- toolGroup.setToolActive(ZoomTool.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 function activateLineGrayscaleMeasurement(viewportId: string): boolean {
- console.log(`[activateLineGrayscaleMeasurement] Activating LineGrayscale measurement for viewport: ${viewportId}`);
- return MeasurementToolManager.activateLineGrayscaleMeasurementTool(viewportId);
- }
- /**
- * 停用直线灰度测量工具
- */
- export function deactivateLineGrayscaleMeasurement(viewportId: string): boolean {
- console.log(`[deactivateLineGrayscaleMeasurement] Deactivating LineGrayscale measurement for viewport: ${viewportId}`);
- return MeasurementToolManager.deactivateLineGrayscaleMeasurementTool(viewportId);
- }
- /**
- * 清除直线灰度测量标注
- */
- export function clearLineGrayscaleMeasurements(viewportId: string): boolean {
- console.log(`[clearLineGrayscaleMeasurements] Clearing LineGrayscale measurements for viewport: ${viewportId}`);
- return MeasurementToolManager.clearLineGrayscaleMeasurements(viewportId);
- }
- // ==================== 矩形区域灰度测量相关函数 ====================
- /**
- * 激活矩形区域灰度测量工具
- */
- export function activateRectangleGrayscaleMeasurement(viewportId: string): boolean {
- console.log(`[activateRectangleGrayscaleMeasurement] Activating RectangleGrayscale measurement for viewport: ${viewportId}`);
- return MeasurementToolManager.activateRectangleGrayscaleMeasurementTool(viewportId);
- }
- /**
- * 停用矩形区域灰度测量工具
- */
- export function deactivateRectangleGrayscaleMeasurement(viewportId: string): boolean {
- console.log(`[deactivateRectangleGrayscaleMeasurement] Deactivating RectangleGrayscale measurement for viewport: ${viewportId}`);
- return MeasurementToolManager.deactivateRectangleGrayscaleMeasurementTool(viewportId);
- }
- /**
- * 清除矩形区域灰度测量标注
- */
- export function clearRectangleGrayscaleMeasurements(viewportId: string): boolean {
- console.log(`[clearRectangleGrayscaleMeasurements] Clearing RectangleGrayscale measurements for viewport: ${viewportId}`);
- return MeasurementToolManager.clearRectangleGrayscaleMeasurements(viewportId);
- }
- // ==================== 掩码工具相关函数 ====================
- /**
- * 激活掩码工具
- */
- export function activateMask(viewportId: string): boolean {
- console.log(`[activateMask] Activating mask tool for viewport: ${viewportId}`);
- return MeasurementToolManager.activateMaskTool(viewportId);
- }
- /**
- * 停用掩码工具
- */
- export function deactivateMask(viewportId: string): boolean {
- console.log(`[deactivateMask] Deactivating mask tool for viewport: ${viewportId}`);
- return MeasurementToolManager.deactivateMaskTool(viewportId);
- }
- /**
- * 清除掩码标注
- */
- export function clearMask(viewportId: string): boolean {
- console.log(`[clearMask] Clearing mask annotations for viewport: ${viewportId}`);
- return MeasurementToolManager.clearMaskAnnotations(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<void> {
- 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<never>((_, 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<HTMLDivElement>(null);
- // 用于捕获异步错误并在渲染时抛出,让 Error Boundary 能够捕获
- const [renderError, setRenderError] = React.useState<Error | null>(null);
- // 获取overlay启用状态
- const overlayEnabled = useSelector(selectOverlayEnabled);
- // 获取 dispatch
- 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. 多帧DICOM预处理:检测并扩展imageIds
- let finalImageUrls = imageUrls;
- let detectedFrameCount = 1;
- // 只有当imageUrls只有一个元素时,才可能是多帧DICOM需要expansion
- if (imageUrls.length === 1) {
- try {
- // 预先分析DICOM元数据以检测帧数
- const { DicomMetadataAnalyzer } = await import('@/utils/dicom/DicomMetadataAnalyzer');
- const quickAnalysis = await DicomMetadataAnalyzer.analyze(imageUrls[0], undefined, {
- baseUrl: getIpPort()
- });
- detectedFrameCount = quickAnalysis.frameCount;
- // 如果检测到多帧,扩展imageIds数组
- if (detectedFrameCount > 1) {
- console.log(`[StackViewer] 检测到多帧DICOM (${detectedFrameCount}帧),扩展imageIds...`);
- const baseImageId = imageUrls[0];
- const expandedImageIds: string[] = [];
- for (let i = 0; i < detectedFrameCount; i++) {
- // 为每一帧创建对应的imageId
- expandedImageIds.push(`${baseImageId}${baseImageId.includes('?') ? '&' : '?'}frame=${i}`);
- }
- finalImageUrls = expandedImageIds;
- console.log(`[StackViewer] ImageIds已扩展:`, {
- 原始: 1,
- 扩展后: expandedImageIds.length,
- 示例: expandedImageIds.slice(0, Math.min(3, expandedImageIds.length))
- });
- }
- } catch (error) {
- console.warn(`[StackViewer] 多帧预检测失败,使用原始imageUrls:`, error);
- }
- }
- // 5. 设置图像栈,使用扩展后的imageUrls
- await safeSetStack(viewport, finalImageUrls, imageIndex);
- // 5. 初始化播放状态(用于多帧播放控制)
- // 使用 DicomMetadataAnalyzer 进行深度分析,从 DICOM 元数据读取帧数
- let totalFrames: number; // Declare totalFrames here
- try {
- // 导入 DicomMetadataAnalyzer (需要在文件顶部添加 import)
- const { DicomMetadataAnalyzer } = await import('@/utils/dicom/DicomMetadataAnalyzer');
- // 使用第一个图像进行分析
- const imageId = imageUrls[imageIndex];
- // 使用 DicomMetadataAnalyzer 进行完整分析
- const analysisResult = await DicomMetadataAnalyzer.analyze(imageId, viewport, {
- baseUrl: getIpPort()
- });
- // 获取 viewport 的实际 imageIds(用于交叉验证)
- const actualImageIds = viewport.getImageIds();
- const viewportFrameCount = actualImageIds.length;
- // 混合验证:比较元数据帧数与 viewport 帧数
- const metadataFrameCount = analysisResult.frameCount;
- totalFrames = metadataFrameCount;//viewportFrameCount; // Assign value here // 优先使用 viewport 的值,它反映实际加载情况
- // 验证一致性
- const isConsistent = metadataFrameCount === viewportFrameCount;
- if (!isConsistent) {
- console.warn(`[StackViewer] 帧数不一致检测:`, {
- imageId,
- metadataFrames: metadataFrameCount,
- viewportFrames: viewportFrameCount,
- analysisConfidence: analysisResult.confidence
- });
- }
- console.log(`[StackViewer] DICOM分析完成:`, {
- imageUrlsCount: imageUrls.length,
- metadataFrames: metadataFrameCount,
- viewportFrames: viewportFrameCount,
- finalTotalFrames: totalFrames,
- isMultiFrame: totalFrames > 1,
- frameType: analysisResult.frameType,
- isEnhanced: analysisResult.isEnhanced,
- confidence: analysisResult.confidence,
- isConsistent
- });
- dispatch(initPlayback({
- viewportId,
- totalFrames
- }));
- // 标记帧检测完成,使用完整的分析结果
- dispatch(setFrameDetectionResult({
- viewportId,
- isMultiFrame: totalFrames > 1,
- totalFrames,
- analysisResult: {
- ...analysisResult,
- // 使用 viewport 的实际帧数覆盖分析结果
- frameCount: totalFrames,
- isMultiFrame: totalFrames > 1
- }
- }));
- } catch (error) {
- // 如果 DicomMetadataAnalyzer 失败,降级到简单检测
- console.warn(`[StackViewer] DICOM分析失败,降级到简单检测:`, error);
- const actualImageIds = viewport.getImageIds();
- totalFrames = actualImageIds.length; // Assign value here
- console.log(`[StackViewer] 使用简单检测方法:`, {
- imageUrlsCount: imageUrls.length,
- actualFrames: totalFrames,
- isMultiFrame: totalFrames > 1
- });
- dispatch(initPlayback({
- viewportId,
- totalFrames
- }));
- dispatch(setFrameDetectionResult({
- viewportId,
- isMultiFrame: totalFrames > 1,
- totalFrames,
- analysisResult: {
- frameCount: totalFrames,
- isMultiFrame: totalFrames > 1,
- frameType: 'dynamic',
- isEnhanced: false,
- analysisTimestamp: new Date(),
- analysisVersion: '1.0.0',
- confidence: 0.5 // 降级检测的可信度较低
- }
- }));
- }
- console.log(`[StackViewer] 播放状态初始化完成: ${totalFrames} 帧`); // This log statement is now outside the inner try-catch
- // 6. 图像加载完成后,自动应用保持宽高比的适应
- 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();
- // ✅ 添加清理函数:确保组件卸载时正确清理 viewport
- return () => {
- try {
- const renderingEngine = cornerstone.getRenderingEngine(renderingEngineId);
- if (renderingEngine) {
- console.log(`[StackViewer] Cleaning up viewport: ${viewportId}`);
- renderingEngine.disableElement(viewportId);
- }
- } catch (error) {
- console.warn(`[StackViewer] Error disabling element ${viewportId}:`, error);
- }
- };
- }, [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 (
- <div
- id={viewportId}
- ref={elementRef}
- onContextMenu={(e) => e.preventDefault()}
- style={{
- width: '100%',
- height: '100%',
- backgroundColor: '#000',
- border: selected ? '2px solid blue' : '1px solid gray',
- position: 'relative', // 让浮动控制条能够正确定位
- }}
- >
- {/* 播放控制器(逻辑组件,不渲染UI) */}
- <PlaybackController
- viewportId={viewportId}
- renderingEngineId={renderingEngineId}
- enabled={true}
- />
- {/* 浮动播放控制条(UI组件) */}
- <FloatingPlaybackControls
- viewportId={viewportId}
- position="bottom"
- autoHide={true}
- showAdvancedControls={true}
- />
- </div>
- );
- };
- /**
- * 带错误边界的 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 (
- <ImageViewerErrorBoundary
- maxRetries={maxRetries}
- onError={onError}
- >
- <StackViewer
- imageIndex={imageIndex}
- imageUrls={imageUrls}
- viewportId={viewportId}
- renderingEngineId={renderingEngineId}
- selected={selected}
- />
- </ImageViewerErrorBoundary>
- );
- };
- export default StackViewer;
|