矩形工具更改
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
parent
b2645f6423
commit
4ff6416edb
|
@ -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 ClinicalData from '@/views/trials/trials-panel/reading/clinical-data'
|
||||||
import FusionForm from './FusionForm.vue'
|
import FusionForm from './FusionForm.vue'
|
||||||
import colorMap from './colorMap.vue'
|
import colorMap from './colorMap.vue'
|
||||||
|
import RectangleROITool from './tools/RectangleROITool'
|
||||||
const { visibility } = annotation
|
const { visibility } = annotation
|
||||||
const { ViewportType, Events } = Enums
|
const { ViewportType, Events } = Enums
|
||||||
const renderingEngineId = 'myRenderingEngine'
|
const renderingEngineId = 'myRenderingEngine'
|
||||||
|
@ -579,7 +580,7 @@ const {
|
||||||
LengthTool,
|
LengthTool,
|
||||||
BidirectionalTool,
|
BidirectionalTool,
|
||||||
ArrowAnnotateTool,
|
ArrowAnnotateTool,
|
||||||
RectangleROITool,
|
// RectangleROITool,
|
||||||
PlanarFreehandROITool,
|
PlanarFreehandROITool,
|
||||||
CircleROITool,
|
CircleROITool,
|
||||||
EraserTool,
|
EraserTool,
|
||||||
|
|
|
@ -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;
|
Loading…
Reference in New Issue