UNPKG

@cornerstonejs/core

Version:
394 lines (393 loc) • 13.1 kB
import Events from '../../enums/Events.js'; import ViewportStatus from '../../enums/ViewportStatus.js'; import triggerEvent from '../../utilities/triggerEvent.js'; import renderingEngineCache from '../renderingEngineCache.js'; import { isGenericViewportReferenceViewable, } from './genericViewportReferenceCompatibility.js'; class GenericViewport { constructor(args) { this.viewportStatus = ViewportStatus.NO_DATA; this.bindings = new Map(); this.dataPresentation = new Map(); this.isDestroyed = false; this._debug = { renderModes: {}, }; this.id = args.id; this.element = args.element; } async setDisplaySets(...entries) { this.removeAllData(); for (const [index, { displaySetId, options }] of entries.entries()) { if (!options) { throw new Error(`[${this.type}] setDisplaySets requires per-entry options when the viewport family does not override it.`); } const dataOptions = options; await this.addDisplaySet(displaySetId, { ...dataOptions, role: dataOptions.role ?? (index === 0 ? 'source' : 'overlay'), }); } } async addDisplaySet(displaySetId, options) { if (this.isDestroyed) { throw new Error('Viewport has been destroyed'); } const data = await this.dataProvider.load(displaySetId, options); await this.addLoadedData(displaySetId, data, options); } getDisplaySets() { return Array.from(this.bindings.entries()).map(([displaySetId, binding]) => ({ displaySetId, options: { role: binding.role }, })); } removeData(displaySetId) { const binding = this.bindings.get(displaySetId); if (!binding) { return; } binding.removeData(); this.bindings.delete(displaySetId); this.dataPresentation.delete(displaySetId); delete this._debug.renderModes[displaySetId]; if (!this.isDestroyed) { this.render(); } } setDisplaySetPresentation(displaySetIdOrProps, maybeProps) { if (typeof displaySetIdOrProps === 'string') { this.mergeDataPresentation(displaySetIdOrProps, maybeProps ?? {}); return; } const defaultId = this.getCurrentBinding()?.data.id; if (!defaultId) { return; } this.mergeDataPresentation(defaultId, displaySetIdOrProps); } getDisplaySetPresentation(displaySetId) { return this.getDataPresentationState(displaySetId); } getCurrentMode() { return this.getSourceBinding() ? 'unknown' : 'empty'; } getViewReference(_specifier = {}) { return { FrameOfReferenceUID: this.getFrameOfReferenceUID(), dataId: this.getCurrentBinding()?.data.id, }; } getViewReferenceId(_specifier = {}) { return `frameOfReference:${this.getFrameOfReferenceUID()}`; } setViewReference(_viewReference) { } isReferenceViewable(viewReference, options = {}) { return isGenericViewportReferenceViewable(viewReference, this.getReferenceViewContexts(viewReference), options); } getFrameOfReferenceUID() { return (this.getResolvedView()?.getFrameOfReferenceUID() ?? `${this.type}-viewport-${this.id}`); } getRenderingEngine() { return renderingEngineCache.get(this.renderingEngineId); } canvasToWorld(canvasPos) { const cc = this.getResolvedView(); if (!cc) { throw new Error(`[${this.type}] Cannot convert canvas to world for viewport ${this.id} because no data is mounted.`); } return cc.canvasToWorld(canvasPos); } worldToCanvas(worldPos) { const cc = this.getResolvedView(); if (!cc) { throw new Error(`[${this.type}] Cannot convert world to canvas for viewport ${this.id} because no data is mounted.`); } return cc.worldToCanvas(worldPos); } getAspectRatio() { return [1, 1]; } setViewState(viewStatePatch) { if (this.isDestroyed) { return; } const previousCamera = this.getCameraForEvent(); const next = { ...this.viewState, ...viewStatePatch, }; this.viewState = this.normalizeViewState(next); this.modified(previousCamera); } getViewState() { return this.viewState; } updateViewState(updater) { const patch = typeof updater === 'function' ? updater(this.getViewState()) : updater; if (!patch) { return; } this.setViewState(patch); } removeWidgets() { } destroy() { if (this.isDestroyed) { return; } this.isDestroyed = true; this.destroyBindings(); this.onDestroy(); this.bindings.clear(); this.dataPresentation.clear(); for (const key of Object.keys(this._debug.renderModes)) { delete this._debug.renderModes[key]; } this.element.removeAttribute('data-viewport-uid'); this.element.removeAttribute('data-rendering-engine-uid'); } dispose() { this.destroy(); } setRendered() { if (this.viewportStatus === ViewportStatus.NO_DATA || this.viewportStatus === ViewportStatus.LOADING) { return; } this.viewportStatus = ViewportStatus.RENDERED; } setNeedsRender() { this.viewportStatus = ViewportStatus.NEEDS_RENDER; } updateRenderingPipeline() { } resize() { if (this.isDestroyed) { return; } this.resizeBindings(); this.modified(); } resetViewState(_options) { return false; } resizeForRenderingEngine({ keepCamera = true, } = {}) { if (this.isDestroyed) { return; } this.resize(); if (!keepCamera) { this.resetViewState(); } } async addLoadedData(displaySetId, data, options, shouldIgnore) { if (this.isDestroyed) { throw new Error('Viewport has been destroyed'); } if (shouldIgnore?.()) { return false; } const path = this.renderPathResolver.resolve(this.type, data, options); const renderPath = path.createRenderPath(); const ctx = path.selectContext?.(this.renderContext) ?? this.renderContext; const attachment = await renderPath.addData(ctx, data, options); if (shouldIgnore?.()) { attachment.removeData(); return false; } if (this.isDestroyed) { attachment.removeData(); throw new Error('Viewport has been destroyed'); } const current = this.bindings.get(displaySetId); if (current) { current.removeData(); } const role = options.role ?? 'overlay'; if (role === 'source') { for (const [bindingDisplaySetId, binding] of this.bindings.entries()) { if (bindingDisplaySetId !== displaySetId) { binding.role = 'overlay'; } } } this.bindings.set(displaySetId, { data, role, ...attachment, }); const binding = this.bindings.get(displaySetId); if (!binding) { throw new Error(`Failed to bind rendering for ${displaySetId}`); } const props = this.dataPresentation.get(displaySetId); if (props !== undefined) { binding.updateDataPresentation(props); } binding.applyViewState(this.viewState); this._debug.renderModes[displaySetId] = attachment.rendering.renderMode; this.viewportStatus = ViewportStatus.PRE_RENDER; this.render(); return true; } removeAllData() { for (const displaySetId of Array.from(this.bindings.keys())) { this.removeData(displaySetId); } } setDataPresentationState(displaySetId, props) { if (this.isDestroyed) { return; } this.dataPresentation.set(displaySetId, props); const binding = this.bindings.get(displaySetId); if (!binding) { return; } binding.updateDataPresentation(props); this.render(); } getDataPresentationState(displaySetId) { return this.dataPresentation.get(displaySetId); } setDefaultDataPresentation(displaySetId, defaults) { const nextPresentation = { ...defaults, ...(this.getDataPresentationState(displaySetId) || {}), }; this.setDataPresentationState(displaySetId, nextPresentation); return nextPresentation; } mergeDataPresentation(displaySetId, props) { const nextPresentation = { ...(this.getDataPresentationState(displaySetId) || {}), ...props, }; this.setDataPresentationState(displaySetId, nextPresentation); if (this.bindings.has(displaySetId)) { this.notifyDataPresentationModified(displaySetId, props); } return nextPresentation; } notifyDataPresentationModified(_displaySetId, _props) { } normalizeViewState(viewState) { return viewState; } modified(previousCamera) { if (this.isDestroyed) { return; } this.forEachBinding((binding) => { binding.applyViewState(this.viewState); }); this.render(); if (previousCamera) { this.triggerCameraModifiedEvent(previousCamera); } } getCameraForEvent() { return (this.getResolvedView()?.toICamera() ?? this.getViewState()); } triggerCameraModifiedEvent(previousCamera) { const eventDetail = { previousCamera, camera: this.getCameraForEvent(), element: this.element, viewportId: this.id, renderingEngineId: this.renderingEngineId, }; triggerEvent(this.element, Events.CAMERA_MODIFIED, eventDetail); } triggerCameraResetEvent() { const eventDetail = { element: this.element, viewportId: this.id, camera: this.getCameraForEvent(), renderingEngineId: this.renderingEngineId, }; triggerEvent(this.element, Events.CAMERA_RESET, eventDetail); } getBinding(displaySetId) { return this.bindings.get(displaySetId); } getDisplaySetRenderMode(displaySetId) { return this.getBinding(displaySetId)?.rendering.renderMode; } getDisplaySetRole(displaySetId) { return this.getBinding(displaySetId)?.role; } getFirstBinding() { return this.bindings.values().next().value; } getSourceBinding() { for (const binding of this.bindings.values()) { if (binding.role === 'source') { return binding; } } return this.getFirstBinding(); } getCurrentBinding() { return this.getFirstBinding(); } forEachBinding(visitor) { for (const binding of this.bindings.values()) { visitor(binding); } } getReferenceViewContexts(_viewReference) { const contexts = []; const resolvedView = this.getResolvedView(); const camera = resolvedView?.toICamera(); for (const [dataId, binding] of this.bindings.entries()) { contexts.push({ dataId, dataIds: [binding.data.id], frameOfReferenceUID: binding.getFrameOfReferenceUID() ?? resolvedView?.getFrameOfReferenceUID() ?? this.getFrameOfReferenceUID(), cameraFocalPoint: camera?.focalPoint, viewPlaneNormal: camera?.viewPlaneNormal, }); } if (!contexts.length) { contexts.push({ frameOfReferenceUID: this.getFrameOfReferenceUID(), cameraFocalPoint: camera?.focalPoint, viewPlaneNormal: camera?.viewPlaneNormal, }); } return contexts; } renderBindings() { if (this.isDestroyed) { return false; } let renderedByAdapter = false; this.forEachBinding((binding) => { binding.render?.(); renderedByAdapter = renderedByAdapter || Boolean(binding.render); }); return renderedByAdapter; } resizeBindings() { if (this.isDestroyed) { return; } this.forEachBinding((binding) => { binding.resize?.(); }); } destroyBindings() { for (const dataId of Array.from(this.bindings.keys())) { this.removeData(dataId); } } onDestroy() { } } export default GenericViewport;