矩形工具更改
continuous-integration/drone/push Build is passing Details

uat
caiyiling 2025-05-12 16:45:48 +08:00
parent b2645f6423
commit 4ff6416edb
2 changed files with 308 additions and 1 deletions

View File

@ -563,6 +563,7 @@ import Others from '@/views/trials/trials-panel/reading/dicoms/components/Others
import ClinicalData from '@/views/trials/trials-panel/reading/clinical-data'
import FusionForm from './FusionForm.vue'
import colorMap from './colorMap.vue'
import RectangleROITool from './tools/RectangleROITool'
const { visibility } = annotation
const { ViewportType, Events } = Enums
const renderingEngineId = 'myRenderingEngine'
@ -579,7 +580,7 @@ const {
LengthTool,
BidirectionalTool,
ArrowAnnotateTool,
RectangleROITool,
// RectangleROITool,
PlanarFreehandROITool,
CircleROITool,
EraserTool,

View File

@ -0,0 +1,306 @@
import {
VolumeViewport,
utilities as csUtils,
} from '@cornerstonejs/core'
import * as cornerstoneTools from '@cornerstonejs/tools'
const {
utilities,
annotation,
drawing
} = cornerstoneTools
const { getAnnotations } = annotation.state
const { isAnnotationVisible } = annotation.visibility
const { isAnnotationLocked } = annotation.locking
const drawHandlesSvg = drawing.drawHandles
const drawRectSvg = drawing.drawRectByCoordinates
const drawLinkedTextBoxSvg = drawing.drawLinkedTextBox
const { getTextBoxCoordsCanvas } = utilities.drawing
/**
* RectangleROIAnnotation let you draw annotations that measures the statistics
* such as area, max, mean and stdDev of a Rectangular region of interest.
* You can use RectangleROIAnnotation in all perpendicular views (axial, sagittal, coronal).
* Note: annotation tools in cornerstone3DTools exists in the exact location
* in the physical 3d space, as a result, by default, all annotations that are
* drawing in the same frameOfReference will get shared between viewports that
* are in the same frameOfReference. RectangleROI tool's text box lines are dynamically
* generated based on the viewport's underlying Modality. For instance, if
* the viewport is displaying CT, the text box will shown the statistics in Hounsfield units,
* and if the viewport is displaying PET, the text box will show the statistics in
* SUV units.
*
* The resulting annotation's data (statistics) and metadata (the
* state of the viewport while drawing was happening) will get added to the
* ToolState manager and can be accessed from the ToolState by calling getAnnotations
* or similar methods.
*
* ```js
* cornerstoneTools.addTool(RectangleROITool)
*
* const toolGroup = ToolGroupManager.createToolGroup('toolGroupId')
*
* toolGroup.addTool(RectangleROITool.toolName)
*
* toolGroup.addViewport('viewportId', 'renderingEngineId')
*
* toolGroup.setToolActive(RectangleROITool.toolName, {
* bindings: [
* {
* mouseButton: MouseBindings.Primary, // Left Click
* },
* ],
* })
* ```
*
* Read more in the Docs section of the website.
*/
class RectangleROITool extends cornerstoneTools.RectangleROITool {
constructor(
toolProps,
defaultToolProps
) {
super(toolProps, defaultToolProps);
}
/**
* it is used to draw the rectangleROI 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
) => {
console.log(drawing)
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, data } = annotation;
const { points, activeHandleIndex } = data.handles;
const canvasCoordinates = points.map((p) => viewport.worldToCanvas(p));
styleSpecifier.annotationUID = annotationUID;
const { color, lineWidth, lineDash } = this.getAnnotationStyle({
annotation,
styleSpecifier,
});
const { viewPlaneNormal, viewUp } = viewport.getCamera();
// If cachedStats does not exist, or the unit is missing (as part of import/hydration etc.),
// force to recalculate the stats from the points
if (
!data.cachedStats[targetId] ||
data.cachedStats[targetId].areaUnit == null
) {
data.cachedStats[targetId] = {
Modality: null,
area: null,
max: null,
mean: null,
stdDev: null,
areaUnit: null,
};
this._calculateCachedStats(
annotation,
viewPlaneNormal,
viewUp,
renderingEngine,
enabledElement
);
} else if (annotation.invalidated) {
this._throttledCalculateCachedStats(
annotation,
viewPlaneNormal,
viewUp,
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;
}
let activeHandleCanvasCoords;
if (!isAnnotationVisible(annotationUID)) {
continue;
}
if (
!isAnnotationLocked(annotationUID) &&
!this.editData &&
activeHandleIndex !== null && activeHandleIndex !== undefined
) {
// Not locked or creating and hovering over handle, so render handle.
activeHandleCanvasCoords = [canvasCoordinates[activeHandleIndex]];
}
if (activeHandleCanvasCoords) {
const handleGroupUID = '0';
drawHandlesSvg(
svgDrawingHelper,
annotationUID,
handleGroupUID,
activeHandleCanvasCoords,
{
color,
}
);
}
const dataId = `${annotationUID}-rect`;
const rectangleUID = '0';
drawRectSvg(
svgDrawingHelper,
annotationUID,
rectangleUID,
canvasCoordinates,
{
color,
lineDash,
lineWidth,
},
dataId
);
renderStatus = true;
const options = this.getLinkedTextBoxStyle(styleSpecifier, annotation);
if (!options.visibility) {
data.handles.textBox = {
hasMoved: false,
worldPosition: [0, 0, 0],
worldBoundingBox: {
topLeft: [0, 0, 0],
topRight: [0, 0, 0],
bottomLeft: [0, 0, 0],
bottomRight: [0, 0, 0],
},
};
continue;
}
const textLines = this.configuration.getTextLines(data, targetId);
if (!textLines || textLines.length === 0) {
continue;
}
if (!data.handles.textBox.hasMoved) {
const canvasTextBoxCoords = getTextBoxCoordsCanvas(canvasCoordinates);
data.handles.textBox.worldPosition =
viewport.canvasToWorld(canvasTextBoxCoords);
}
const textBoxPosition = viewport.worldToCanvas(
data.handles.textBox.worldPosition
);
const textBoxUID = '1';
const boundingBox = drawLinkedTextBoxSvg(
svgDrawingHelper,
annotationUID,
textBoxUID,
textLines,
textBoxPosition,
canvasCoordinates,
{},
options
);
const { x: left, y: top, width, height } = boundingBox;
data.handles.textBox.worldBoundingBox = {
topLeft: viewport.canvasToWorld([left, top]),
topRight: viewport.canvasToWorld([left + width, top]),
bottomLeft: viewport.canvasToWorld([left, top + height]),
bottomRight: viewport.canvasToWorld([left + width, top + height]),
};
}
return renderStatus;
};
}
export default RectangleROITool;