基于cornerstone3D的dicom影像浏览器 第四章 鼠标实现翻页、放大、移动、窗宽窗位调节

发布于:2025-09-06 ⋅ 阅读:(15) ⋅ 点赞:(0)

先看效果

dicom鼠标调节

1.定义utils/initTools.js工具函数

import { Enums, Settings } from "@cornerstonejs/core";
import * as cornerstoneTools from "@cornerstonejs/tools";
import MyProbeTool from "./MyProbeTool.js";
const {
	annotation
} = cornerstoneTools;
cornerstoneTools.init()
const {
	ToolGroupManager,
	Enums: csToolsEnums,
	StackScrollTool,
	WindowLevelTool,
	PanTool,
	ZoomTool,
} = cornerstoneTools;
cornerstoneTools.addTool(MyProbeTool);

cornerstoneTools.addTool(StackScrollTool);
cornerstoneTools.addTool(WindowLevelTool);
cornerstoneTools.addTool(PanTool);
cornerstoneTools.addTool(ZoomTool);


const { MouseBindings } = csToolsEnums;
const toolGroupId = "tpid_2d";
const toolGroup = ToolGroupManager.createToolGroup(toolGroupId);

function initTools() {
	// Add tools to the tool group
	toolGroup.addTool(WindowLevelTool.toolName);
	toolGroup.addTool(PanTool.toolName);
	toolGroup.addTool(MyProbeTool.toolName);
	
	

	toolGroup.addTool(ZoomTool.toolName, {
		zoomToCenter: true,
		invert: true,
		minZoomScale: 0.1,
		maxZoomScale: 20,
		preventDefault: true
	});
	toolGroup.addTool(StackScrollTool.toolName);
	

	toolGroup.setToolActive(WindowLevelTool.toolName, {
		bindings: [
			{
				mouseButton: MouseBindings.Primary // Left Click
			}
		]
	});
	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 // Wheel Mouse
			}
		]
	})
}


export { initTools, toolGroup };

2.MyProbeTool.js

import * as cornerstoneTools from "@cornerstonejs/tools";
import { VolumeViewport, utilities as csUtils } from "@cornerstonejs/core";
import drawHandlesSvg from "./drawHandlesSvg.js";

const {
	Enums: csToolsEnums,
	ProbeTool,
	annotation,
	drawing
} = cornerstoneTools;

const { ChangeTypes } = csToolsEnums;
const { getAnnotations } = annotation.state;
const { drawTextBox: drawTextBoxSvg } = drawing;

class MyProbeTool extends ProbeTool {
	static toolName = "MyProbe";

	constructor(options = {}) {
		super(options);
	}

	/**
	 * it is used to draw the probe annotation in each
	 * request animation frame. It calculates the updated cached statistics if
	 * data is invalidated and cache it.
	 *
	 * @param enabledElement - The Cornerstone's enabledElement.
	 * @param svgDrawingHelper - The svgDrawingHelper providing the context for drawing.
	 */
	renderAnnotation = (enabledElement, svgDrawingHelper) => {
		let renderStatus = false;
		const { viewport } = enabledElement;
		const { element } = viewport;

		let annotations = getAnnotations(this.getToolName(), element);

		if (!annotations?.length) {
			return renderStatus;
		}

		annotations = this.filterInteractableAnnotationsForElement(
			element,
			annotations
		);

		if (!annotations?.length) {
			return renderStatus;
		}

		const targetId = this.getTargetId(viewport);
		const renderingEngine = viewport.getRenderingEngine();

		const styleSpecifier = {
			toolGroupId: this.toolGroupId,
			toolName: this.getToolName(),
			viewportId: enabledElement.viewport.id
		};

		for (let i = 0; i < annotations.length; i++) {
			const annotation = annotations[i];
			const annotationUID = annotation.annotationUID;
			const data = annotation.data;
			const point = data.handles.points[0];
			const canvasCoordinates = viewport.worldToCanvas(point);

			styleSpecifier.annotationUID = annotationUID;

			const { color, lineWidth } = this.getAnnotationStyle({
				annotation,
				styleSpecifier
			});

			if (!data.cachedStats) {
				data.cachedStats = {};
			}

			if (
				!data.cachedStats[targetId] ||
				data.cachedStats[targetId].value === null
			) {
				data.cachedStats[targetId] = {
					Modality: null,
					index: null,
					value: null
				};

				this._calculateCachedStats(
					annotation,
					renderingEngine,
					enabledElement,
					ChangeTypes.StatsUpdated
				);
			} else if (annotation.invalidated) {
				this._calculateCachedStats(
					annotation,
					renderingEngine,
					enabledElement
				);

				// If the invalidated data is as a result of volumeViewport manipulation
				// of the tools, we need to invalidate the related stackViewports data if
				// they are not at the referencedImageId, so that
				// when scrolling to the related slice in which the tool were manipulated
				// we re-render the correct tool position. This is due to stackViewport
				// which doesn't have the full volume at each time, and we are only working
				// on one slice at a time.
				if (viewport instanceof VolumeViewport) {
					const { referencedImageId } = annotation.metadata;

					// invalidate all the relevant stackViewports if they are not
					// at the referencedImageId
					for (const targetId in data.cachedStats) {
						if (targetId.startsWith("imageId")) {
							const viewports =
								renderingEngine.getStackViewports();

							const invalidatedStack = viewports.find(vp => {
								// The stack viewport that contains the imageId but is not
								// showing it currently
								const referencedImageURI =
									csUtils.imageIdToURI(referencedImageId);
								const hasImageURI =
									vp.hasImageURI(referencedImageURI);
								const currentImageURI = csUtils.imageIdToURI(
									vp.getCurrentImageId()
								);
								return (
									hasImageURI &&
									currentImageURI !== referencedImageURI
								);
							});

							if (invalidatedStack) {
								delete data.cachedStats[targetId];
							}
						}
					}
				}
			}

			// If rendering engine has been destroyed while rendering
			if (!viewport.getRenderingEngine()) {
				console.warn("Rendering Engine has been destroyed");
				return renderStatus;
			}

			const handleGroupUID = "0";
            // 重写此函数
			drawHandlesSvg(
				svgDrawingHelper,
				annotationUID,
				handleGroupUID,
				[canvasCoordinates],
				{
					color,
					lineWidth,
					handleRadius: this.configuration.handleRadius,
					type: "path"
				}
			);

			renderStatus = true;

			const options = this.getLinkedTextBoxStyle(
				styleSpecifier,
				annotation
			);
			if (!options.visibility) {
				continue;
			}

			const textLines = this.configuration.getTextLines(data, targetId);
			if (textLines) {
				const textCanvasCoordinates = [
					canvasCoordinates[0] + 6,
					canvasCoordinates[1] - 6
				];

				const textUID = "0";
				drawTextBoxSvg(
					svgDrawingHelper,
					annotationUID,
					textUID,
					textLines,
					[textCanvasCoordinates[0], textCanvasCoordinates[1]],
					options
				);
			}
		}

		return renderStatus;
	};
}

export default MyProbeTool;

3.drawHandlesSvg.js

// import _getHash from "./_getHash";
// import setNewAttributesIfValid from './setNewAttributesIfValid';
// import setAttributesIfNecessary from "./setAttributesIfNecessary";

function _getHash(annotationUID, drawingElementType, nodeUID) {
	return `${annotationUID}::${drawingElementType}::${nodeUID}`;
}

function setNewAttributesIfValid(attributes, svgNode) {
	Object.keys(attributes).forEach(key => {
		const newValue = attributes[key];
		if (newValue !== undefined && newValue !== "") {
			svgNode.setAttribute(key, newValue);
		}
	});
}

function setAttributesIfNecessary(attributes, svgNode) {
	Object.keys(attributes).forEach(key => {
		const currentValue = svgNode.getAttribute(key);
		const newValue = attributes[key];
		if (newValue === undefined || newValue === "") {
			svgNode.removeAttribute(key);
		} else if (currentValue !== newValue) {
			svgNode.setAttribute(key, newValue);
		}
	});
}

function drawHandlesSvg(
	svgDrawingHelper,
	annotationUID,
	handleGroupUID,
	handlePoints,
	options = {}
) {
	handlePoints.forEach((handle, i) => {
		drawHandle(
			svgDrawingHelper,
			annotationUID,
			handleGroupUID,
			handle,
			options,
			i
		);
	});
}

function drawHandle(
	svgDrawingHelper,
	annotationUID,
	handleGroupUID,
	handle,
	options = {},
	uniqueIndex
) {
	const { color, handleRadius, width, lineWidth, fill, type, opacity } =
		Object.assign(
			{
				color: "rgb(0, 255, 0)",
				handleRadius: "6",
				width: "2",
				lineWidth: undefined,
				fill: "transparent",
				type: "circle", //type: 'circle|rect|path',
				opacity: 1
			},
			options
		);
	// for supporting both lineWidth and width options
	const strokeWidth = lineWidth || width;

	// variable for the namespace
	const svgns = "http://www.w3.org/2000/svg";
	const svgNodeHash = _getHash(
		annotationUID,
		"handle",
		`hg-${handleGroupUID}-index-${uniqueIndex}`
	);

	let attributes;
	if (type === "circle") {
		attributes = {
			cx: `${handle[0]}`,
			cy: `${handle[1]}`,
			r: handleRadius,
			stroke: color,
			fill,
			"stroke-width": strokeWidth,
			opacity: opacity
		};
	} else if (type === "rect") {
		const handleRadiusFloat = parseFloat(handleRadius);
		const side = handleRadiusFloat * 1.5;
		const x = handle[0] - side * 0.5;
		const y = handle[1] - side * 0.5;

		attributes = {
			x: `${x}`,
			y: `${y}`,
			width: `${side}`,
			height: `${side}`,
			stroke: color,
			fill,
			"stroke-width": strokeWidth,
			rx: `${side * 0.1}`,
			opacity: opacity
		};
	} else if (type === "path") {
		const handleRadiusFloat = parseFloat(handleRadius);
		const side = handleRadiusFloat * 1.5;
		const x = handle[0] - side * 0.5;
		const y = handle[1] - side * 0.5;
		const d = `M ${x} ${handle[1]} L ${x + side} ${handle[1]} M ${
			handle[0]
		} ${y} L ${handle[0]} ${y + side}`;
		attributes = {
			d,
			stroke: color,
			fill,
			"stroke-width": strokeWidth,
			opacity: opacity
		};
	} else {
		throw new Error(`Unsupported handle type: ${type}`);
	}

	const existingHandleElement = svgDrawingHelper.getSvgNode(svgNodeHash);

	if (existingHandleElement) {
		setAttributesIfNecessary(attributes, existingHandleElement);

		svgDrawingHelper.setNodeTouched(svgNodeHash);
	} else {
		const newHandleElement = document.createElementNS(svgns, type);

		setNewAttributesIfValid(attributes, newHandleElement);

		svgDrawingHelper.appendNode(newHandleElement, svgNodeHash);
	}
}

export default drawHandlesSvg;

4.在displayerArea.vue调用

import { initTools } from "@/utils/initTools";

onMounted(() => {
    initTools()  
});


网站公告

今日签到

点亮在社区的每一天
去签到