先看效果
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()
});