UNPKG

@cornerstonejs/core

Version:
347 lines (346 loc) • 13.7 kB
import Events from '../enums/Events.js'; import renderingEngineCache from './renderingEngineCache.js'; import eventTarget from '../eventTarget.js'; import uuidv4 from '../utilities/uuidv4.js'; import triggerEvent from '../utilities/triggerEvent.js'; import ViewportType from '../enums/ViewportType.js'; import viewportTypeUsesCustomRenderingPipeline, { viewportUsesCustomRenderingPipeline, } from './helpers/viewportTypeUsesCustomRenderingPipeline.js'; import getOrCreateCanvas from './helpers/getOrCreateCanvas.js'; import { setElementTouchActionNone, restoreElementTouchAction, } from './helpers/elementTouchAction.js'; import { getShouldUseCPURendering, getUseGenericViewport, isCornerstoneInitialized, getConfiguration, } from '../init.js'; import { getViewportClassForInput } from './helpers/viewportTypeToViewportClass.js'; import ViewportStatus from '../enums/ViewportStatus.js'; import { OrientationAxis } from '../enums/index.js'; import { StatsOverlay } from './helpers/stats/index.js'; import { convertColorArrayToRgbString } from '../utilities/convertColorArrayToRgbString.js'; export const VIEWPORT_MIN_SIZE = 2; class BaseRenderingEngine { constructor(id) { this._needsRender = new Set(); this._animationFrameSet = false; this._animationFrameHandle = null; this.renderFrameOfReference = (FrameOfReferenceUID) => { const viewports = this._getViewportsAsArray(); const viewportIdsWithSameFrameOfReferenceUID = viewports.map((vp) => { if (vp.getFrameOfReferenceUID() === FrameOfReferenceUID) { return vp.id; } }); this.renderViewports(viewportIdsWithSameFrameOfReferenceUID); }; this.id = id ? id : uuidv4(); this.useCPURendering = getShouldUseCPURendering(); renderingEngineCache.set(this); if (!isCornerstoneInitialized()) { throw new Error('@cornerstonejs/core is not initialized, run init() first'); } this._viewports = new Map(); this.hasBeenDestroyed = false; const config = getConfiguration(); if (config?.debug?.statsOverlay) { StatsOverlay.setup(); } } enableElement(viewportInputEntry) { const viewportInput = this._normalizeViewportInputEntry(viewportInputEntry); this._throwIfDestroyed(); const { element, viewportId } = viewportInput; if (!element) { throw new Error('No element provided'); } const viewport = this.getViewport(viewportId); if (viewport) { this.disableElement(viewportId); } const { type } = viewportInput; const viewportUsesCustomRenderingPipeline = viewportTypeUsesCustomRenderingPipeline(type); if (!viewportUsesCustomRenderingPipeline) { this.enableVTKjsDrivenViewport(viewportInput); } else { this.addCustomViewport(viewportInput); } const canvas = getOrCreateCanvas(element); const { background } = viewportInput.defaultOptions; this.fillCanvasWithBackgroundColor(canvas, background); } disableElement(viewportId) { this._throwIfDestroyed(); const viewport = this.getViewport(viewportId); if (!viewport) { console.warn(`viewport ${viewportId} does not exist`); return; } this._resetViewport(viewport); if (!viewportTypeUsesCustomRenderingPipeline(viewport.type) && this.offscreenMultiRenderWindow) { this.offscreenMultiRenderWindow.removeRenderer(viewportId); } this._removeViewport(viewportId); viewport.isDisabled = true; this._needsRender.delete(viewportId); const viewports = this.getViewports(); if (!viewports.length) { this._clearAnimationFrame(); } } disableViewport(viewportId) { this.disableElement(viewportId); } setViewports(publicViewportInputEntries) { const viewportInputEntries = this._normalizeViewportInputEntries(publicViewportInputEntries); this._throwIfDestroyed(); this._reset(); const vtkDrivenViewportInputEntries = []; const customRenderingViewportInputEntries = []; viewportInputEntries.forEach((vpie) => { if (!viewportTypeUsesCustomRenderingPipeline(vpie.type)) { vtkDrivenViewportInputEntries.push(vpie); } else { customRenderingViewportInputEntries.push(vpie); } }); this.setVtkjsDrivenViewports(vtkDrivenViewportInputEntries); this.setCustomViewports(customRenderingViewportInputEntries); viewportInputEntries.forEach((vp) => { const canvas = getOrCreateCanvas(vp.element); const { background } = vp.defaultOptions; this.fillCanvasWithBackgroundColor(canvas, background); }); } resize(immediate = true, keepCamera = true) { this._throwIfDestroyed(); const viewports = this._getViewportsAsArray(); const vtkDrivenViewports = []; const customRenderingViewports = []; viewports.forEach((vpie) => { if (!viewportUsesCustomRenderingPipeline(vpie)) { vtkDrivenViewports.push(vpie); } else { customRenderingViewports.push(vpie); } }); if (vtkDrivenViewports.length) { this._resizeVTKViewports(vtkDrivenViewports, keepCamera, immediate); } if (customRenderingViewports.length) { this._resizeUsingCustomResizeHandler(customRenderingViewports, keepCamera, immediate); } } getViewport(viewportId) { return this._viewports?.get(viewportId); } getViewports() { this._throwIfDestroyed(); return this._getViewportsAsArray(); } render() { const viewports = this.getViewports(); const viewportIds = viewports.map((vp) => vp.id); this._setViewportsToBeRenderedNextFrame(viewportIds); } renderViewports(viewportIds) { this._setViewportsToBeRenderedNextFrame(viewportIds); } renderViewport(viewportId) { this._setViewportsToBeRenderedNextFrame([viewportId]); } destroy() { if (this.hasBeenDestroyed) { return; } StatsOverlay.cleanup(); const viewports = this._getViewportsAsArray(); this._reset(); if (this.offscreenMultiRenderWindow) { viewports.forEach((vp) => { if (this.offscreenMultiRenderWindow && !viewportUsesCustomRenderingPipeline(vp) && this.offscreenMultiRenderWindow.getRenderer(vp.id)) { this.offscreenMultiRenderWindow.removeRenderer(vp.id); } }); this.offscreenMultiRenderWindow.delete(); delete this.offscreenMultiRenderWindow; } renderingEngineCache.delete(this.id); this.hasBeenDestroyed = true; } fillCanvasWithBackgroundColor(canvas, backgroundColor) { const ctx = canvas.getContext('2d'); const fillStyle = backgroundColor ? convertColorArrayToRgbString(backgroundColor) : 'black'; ctx.fillStyle = fillStyle; ctx.fillRect(0, 0, canvas.width, canvas.height); } _normalizeViewportInputEntry(viewportInputEntry) { const requestedType = viewportInputEntry.type; let { type } = viewportInputEntry; const { defaultOptions } = viewportInputEntry; if (getUseGenericViewport()) { const remapped = BaseRenderingEngine.NEXT_TYPE_REMAP[type]; if (remapped) { type = remapped; } } let options = defaultOptions; if (!options || Object.keys(options).length === 0) { options = { background: [0, 0, 0], orientation: null, displayArea: null, }; if (requestedType === ViewportType.ORTHOGRAPHIC) { options = { ...options, orientation: OrientationAxis.AXIAL, }; } } return { ...viewportInputEntry, requestedType: requestedType !== type || getUseGenericViewport() ? requestedType : undefined, type, defaultOptions: options, }; } _normalizeViewportInputEntries(viewportInputEntries) { const normalizedViewportInputs = []; viewportInputEntries.forEach((viewportInput) => { normalizedViewportInputs.push(this._normalizeViewportInputEntry(viewportInput)); }); return normalizedViewportInputs; } _resizeUsingCustomResizeHandler(customRenderingViewports, keepCamera = true, immediate = true) { customRenderingViewports.forEach((vp) => { vp.resizeForRenderingEngine({ keepCamera }); }); if (immediate) { this.render(); } } _removeViewport(viewportId) { const viewport = this.getViewport(viewportId); if (!viewport) { console.warn(`viewport ${viewportId} does not exist`); return; } this._viewports.delete(viewportId); } addCustomViewport(viewportInputEntry) { const { element, viewportId, type, defaultOptions } = viewportInputEntry; element.tabIndex = -1; setElementTouchActionNone(element); const canvas = getOrCreateCanvas(element); const { clientWidth, clientHeight } = canvas; if (canvas.width !== clientWidth || canvas.height !== clientHeight) { canvas.width = clientWidth; canvas.height = clientHeight; } const viewportInput = { id: viewportId, renderingEngineId: this.id, element, type, canvas, sx: 0, sy: 0, sWidth: clientWidth, sHeight: clientHeight, defaultOptions: defaultOptions || {}, }; const ViewportClass = getViewportClassForInput(viewportInputEntry); const viewport = new ViewportClass(viewportInput); viewport.requestedType = type; this._viewports.set(viewportId, viewport); const eventDetail = { element, viewportId, renderingEngineId: this.id, }; triggerEvent(eventTarget, Events.ELEMENT_ENABLED, eventDetail); } getRenderer(viewportId) { return this.offscreenMultiRenderWindow.getRenderer(viewportId); } getOffscreenMultiRenderWindow(_viewportId) { if (!this.offscreenMultiRenderWindow) { throw new Error('Offscreen multi render window is not available.'); } return this.offscreenMultiRenderWindow; } setCustomViewports(viewportInputEntries) { viewportInputEntries.forEach((vpie) => { this.addCustomViewport(vpie); }); } _getViewportsAsArray() { return Array.from(this._viewports.values()); } _setViewportsToBeRenderedNextFrame(viewportIds) { viewportIds.forEach((viewportId) => { this._needsRender.add(viewportId); const viewport = this._viewports.get(viewportId); if (viewport) { viewport.viewportStatus = ViewportStatus.NEEDS_RENDER; } }); this._render(); } _render() { if (this._needsRender.size > 0 && !this._animationFrameSet) { this._animationFrameHandle = window.requestAnimationFrame(this._renderFlaggedViewports); this._animationFrameSet = true; } } _resetViewport(viewport) { const renderingEngineId = this.id; const { element, canvas, id: viewportId } = viewport; const eventDetail = { element, viewportId, renderingEngineId, }; viewport.removeWidgets(); triggerEvent(eventTarget, Events.ELEMENT_DISABLED, eventDetail); viewport.destroy?.(); element.removeAttribute('data-viewport-uid'); element.removeAttribute('data-rendering-engine-uid'); restoreElementTouchAction(element); const context = canvas.getContext('2d'); context.clearRect(0, 0, canvas.width, canvas.height); } _clearAnimationFrame() { window.cancelAnimationFrame(this._animationFrameHandle); this._needsRender.clear(); this._animationFrameSet = false; this._animationFrameHandle = null; } _reset() { const viewports = this._getViewportsAsArray(); viewports.forEach((viewport) => { this._resetViewport(viewport); }); this._clearAnimationFrame(); this._viewports = new Map(); } _throwIfDestroyed() { if (this.hasBeenDestroyed) { throw new Error('this.destroy() has been manually called to free up memory, can not longer use this instance. Instead make a new one.'); } } } BaseRenderingEngine.NEXT_TYPE_REMAP = { [ViewportType.STACK]: ViewportType.PLANAR_NEXT, [ViewportType.ORTHOGRAPHIC]: ViewportType.PLANAR_NEXT, [ViewportType.VIDEO]: ViewportType.VIDEO_NEXT, [ViewportType.WHOLE_SLIDE]: ViewportType.WHOLE_SLIDE_NEXT, [ViewportType.ECG]: ViewportType.ECG_NEXT, [ViewportType.VOLUME_3D]: ViewportType.VOLUME_3D_NEXT, }; export default BaseRenderingEngine;