From 3f296ae86c89934dfdfc2107945fcba1e08db7df Mon Sep 17 00:00:00 2001 From: Alireza Date: Thu, 3 Mar 2022 15:28:32 -0500 Subject: [PATCH] fix: cachedStatistics throttling and textBox rendering (#329) * fix: throttling of the cachedStats * rebased modifications --- .../src/drawingSvg/drawTextBox.ts | 30 +++++++++---- .../measurementModifiedEventDispatcher.ts | 43 +++++++++++++++++++ .../src/eventListeners/annotations/index.ts | 4 ++ .../measurementModifiedListener.ts | 22 ++++++++++ .../measurementSelectionListener.ts | 0 .../src/eventListeners/index.ts | 6 ++- .../src/eventListeners/toolStyles/index.ts | 3 -- packages/cornerstone-tools/src/init.ts | 13 +++++- 8 files changed, 106 insertions(+), 15 deletions(-) create mode 100644 packages/cornerstone-tools/src/eventDispatchers/measurementModifiedEventDispatcher.ts create mode 100644 packages/cornerstone-tools/src/eventListeners/annotations/index.ts create mode 100644 packages/cornerstone-tools/src/eventListeners/annotations/measurementModifiedListener.ts rename packages/cornerstone-tools/src/eventListeners/{toolStyles => annotations}/measurementSelectionListener.ts (100%) delete mode 100644 packages/cornerstone-tools/src/eventListeners/toolStyles/index.ts diff --git a/packages/cornerstone-tools/src/drawingSvg/drawTextBox.ts b/packages/cornerstone-tools/src/drawingSvg/drawTextBox.ts index 28e2f38c4..99105bd63 100644 --- a/packages/cornerstone-tools/src/drawingSvg/drawTextBox.ts +++ b/packages/cornerstone-tools/src/drawingSvg/drawTextBox.ts @@ -1,6 +1,6 @@ import _getHash from './_getHash' import { Point2 } from '../types' -import _setNewAttributesIfValid from './_setNewAttributesIfValid' +import _setAttributesIfNecessary from './_setAttributesIfNecessary' /** * Draws a textBox. @@ -63,8 +63,9 @@ function _drawTextGroup( const svgNodeHash = _getHash(toolName, annotationUID, 'text', textUID) const existingTextGroup = svgDrawingHelper._getSvgNode(svgNodeHash) + // Todo: right now textBox gets a re-render even if the textBox has not changed + // and evenIf the attributes are not set again since they are the same. if (existingTextGroup) { - existingTextGroup.setAttribute('transform', `translate(${x} ${y})`) // TODO: Iterate each node and update color? font-size? // TODO: Does not support change in # of text lines const textElement = existingTextGroup.querySelector('text') @@ -77,13 +78,19 @@ function _drawTextGroup( textSpanElement.textContent = text } - const attributes = { + const textAttributes = { fill: color, 'font-size': fontSize, 'font-family': fontFamily, } - _setNewAttributesIfValid(attributes, textElement) + const textGroupAttributes = { + transform: `translate(${x} ${y})`, + } + + // Todo: for some reason this does not work to not re-render the textBox + _setAttributesIfNecessary(textAttributes, textElement) + _setAttributesIfNecessary(textGroupAttributes, existingTextGroup) textGroupBoundingBox = _drawTextBackground(existingTextGroup, background) @@ -175,11 +182,16 @@ function _drawTextBackground(group: SVGGElement, color: string) { // Get the text groups's bounding box and use it to draw the background rectangle const bBox = group.getBBox() - element.setAttribute('x', `${bBox.x}`) - element.setAttribute('y', `${bBox.y}`) - element.setAttribute('width', `${bBox.width}`) - element.setAttribute('height', `${bBox.height}`) - element.setAttribute('fill', color) + + const attributes = { + x: `${bBox.x}`, + y: `${bBox.y}`, + width: `${bBox.width}`, + height: `${bBox.height}`, + fill: color, + } + + _setAttributesIfNecessary(attributes, element) return bBox } diff --git a/packages/cornerstone-tools/src/eventDispatchers/measurementModifiedEventDispatcher.ts b/packages/cornerstone-tools/src/eventDispatchers/measurementModifiedEventDispatcher.ts new file mode 100644 index 000000000..dc9d68e5a --- /dev/null +++ b/packages/cornerstone-tools/src/eventDispatchers/measurementModifiedEventDispatcher.ts @@ -0,0 +1,43 @@ +import { + eventTarget, + getRenderingEngine, +} from '@precisionmetrics/cornerstone-render' +import EVENTS from '../enums/CornerstoneTools3DEvents' +import triggerAnnotationRenderForViewportUIDs from '../util/triggerAnnotationRenderForViewportUIDs' + +/** + * This is a callback function that is called when a measurement is modified. + * Since we are throttling the cachedStats calculation for annotation tools, + * we need to trigger a final render for the toolData so that the measurement + * textBox is updated. + * Todo: This will trigger all the annotation tools to re-render, although DOM + * will update those that have changed, but more efficient would be to only + * update the changed toolData. + * Todo: A better way is to extract the textBox render logic from the renderToolData + * of all tools and just trigger a render for that (instead of the entire toolData, even if + * no svg update happens since the attributes for handles are the same) + */ +const onMeasurementModified = function (evt) { + const { viewportUID, renderingEngineUID } = evt.detail + const renderingEngine = getRenderingEngine(renderingEngineUID) + triggerAnnotationRenderForViewportUIDs(renderingEngine, [viewportUID]) +} + +const enable = function () { + eventTarget.addEventListener( + EVENTS.MEASUREMENT_MODIFIED, + onMeasurementModified + ) +} + +const disable = function () { + eventTarget.removeEventListener( + EVENTS.MEASUREMENT_MODIFIED, + onMeasurementModified + ) +} + +export default { + enable, + disable, +} diff --git a/packages/cornerstone-tools/src/eventListeners/annotations/index.ts b/packages/cornerstone-tools/src/eventListeners/annotations/index.ts new file mode 100644 index 000000000..e61b9ec62 --- /dev/null +++ b/packages/cornerstone-tools/src/eventListeners/annotations/index.ts @@ -0,0 +1,4 @@ +import measurementSelectionListener from './measurementSelectionListener' +import measurementModifiedListener from './measurementModifiedListener' + +export { measurementSelectionListener, measurementModifiedListener } diff --git a/packages/cornerstone-tools/src/eventListeners/annotations/measurementModifiedListener.ts b/packages/cornerstone-tools/src/eventListeners/annotations/measurementModifiedListener.ts new file mode 100644 index 000000000..08113ec5d --- /dev/null +++ b/packages/cornerstone-tools/src/eventListeners/annotations/measurementModifiedListener.ts @@ -0,0 +1,22 @@ +import { getRenderingEngine } from '@precisionmetrics/cornerstone-render' +import triggerAnnotationRenderForViewportUIDs from '../../util/triggerAnnotationRenderForViewportUIDs' + +/** + * This is a callback function that is called when a measurement is modified. + * Since we are throttling the cachedStats calculation for annotation tools, + * we need to trigger a final render for the toolData so that the measurement + * textBox is updated. + * Todo: This will trigger all the annotation tools to re-render, although DOM + * will update those that have changed, but more efficient would be to only + * update the changed toolData. + * Todo: A better way is to extract the textBox render logic from the renderToolData + * of all tools and just trigger a render for that (instead of the entire toolData, even if + * no svg update happens since the attributes for handles are the same) + */ +function measurementModifiedListener(evt): void { + const { viewportUID, renderingEngineUID } = evt.detail + const renderingEngine = getRenderingEngine(renderingEngineUID) + triggerAnnotationRenderForViewportUIDs(renderingEngine, [viewportUID]) +} + +export default measurementModifiedListener diff --git a/packages/cornerstone-tools/src/eventListeners/toolStyles/measurementSelectionListener.ts b/packages/cornerstone-tools/src/eventListeners/annotations/measurementSelectionListener.ts similarity index 100% rename from packages/cornerstone-tools/src/eventListeners/toolStyles/measurementSelectionListener.ts rename to packages/cornerstone-tools/src/eventListeners/annotations/measurementSelectionListener.ts diff --git a/packages/cornerstone-tools/src/eventListeners/index.ts b/packages/cornerstone-tools/src/eventListeners/index.ts index 27c6ce01e..22cee0c86 100644 --- a/packages/cornerstone-tools/src/eventListeners/index.ts +++ b/packages/cornerstone-tools/src/eventListeners/index.ts @@ -5,7 +5,10 @@ import { segmentationDataModifiedEventListener, segmentationStateModifiedEventListener, } from './segmentation' -import { measurementSelectionListener } from './toolStyles' +import { + measurementSelectionListener, + measurementModifiedListener, +} from './annotations' //import touchEventListeners from './touchEventListeners'; export { @@ -15,4 +18,5 @@ export { segmentationStateModifiedEventListener, segmentationDataModifiedEventListener, measurementSelectionListener, + measurementModifiedListener, } diff --git a/packages/cornerstone-tools/src/eventListeners/toolStyles/index.ts b/packages/cornerstone-tools/src/eventListeners/toolStyles/index.ts deleted file mode 100644 index 800bd44b1..000000000 --- a/packages/cornerstone-tools/src/eventListeners/toolStyles/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import measurementSelectionListener from './measurementSelectionListener' - -export { measurementSelectionListener } diff --git a/packages/cornerstone-tools/src/init.ts b/packages/cornerstone-tools/src/init.ts index 3ad56c689..519acf8f5 100644 --- a/packages/cornerstone-tools/src/init.ts +++ b/packages/cornerstone-tools/src/init.ts @@ -11,6 +11,7 @@ import { measurementSelectionListener, segmentationDataModifiedEventListener, segmentationStateModifiedEventListener, + measurementModifiedListener, } from './eventListeners' import ToolGroupManager from './store/ToolGroupManager' @@ -85,7 +86,7 @@ function _removeCornerstoneEventListeners() { /** * It adds an event listener to the event target (the cornerstoneTools object) for - * the selection event. + * the measurement selected and measurement modified events. */ function _addCornerstoneToolsEventListeners() { // Clear any listeners that may already be set @@ -94,6 +95,7 @@ function _addCornerstoneToolsEventListeners() { const selectionEvent = TOOLS_EVENTS.MEASUREMENT_SELECTION_CHANGE const segmentationDataModified = TOOLS_EVENTS.SEGMENTATION_DATA_MODIFIED const segmentationStateModified = TOOLS_EVENTS.SEGMENTATION_STATE_MODIFIED + const modifiedEvent = TOOLS_EVENTS.MEASUREMENT_MODIFIED eventTarget.addEventListener(selectionEvent, measurementSelectionListener) eventTarget.addEventListener( @@ -104,13 +106,17 @@ function _addCornerstoneToolsEventListeners() { segmentationStateModified, segmentationStateModifiedEventListener ) + + eventTarget.addEventListener(selectionEvent, measurementSelectionListener) + eventTarget.addEventListener(modifiedEvent, measurementModifiedListener) } /** - * Remove the event listener for the selection event + * Remove the event listener for the the measurement selected and measurement modified events. */ function _removeCornerstoneToolsEventListeners() { const selectionEvent = TOOLS_EVENTS.MEASUREMENT_SELECTION_CHANGE + const modifiedEvent = TOOLS_EVENTS.MEASUREMENT_MODIFIED const segmentationDataModified = TOOLS_EVENTS.SEGMENTATION_DATA_MODIFIED const segmentationStateModified = TOOLS_EVENTS.SEGMENTATION_STATE_MODIFIED @@ -123,6 +129,9 @@ function _removeCornerstoneToolsEventListeners() { segmentationStateModified, segmentationStateModifiedEventListener ) + + eventTarget.removeEventListener(selectionEvent, measurementSelectionListener) + eventTarget.removeEventListener(modifiedEvent, measurementModifiedListener) } export default init