UNPKG

@cornerstonejs/core

Version:
423 lines (422 loc) • 18.7 kB
import { vec3 } from 'gl-matrix'; import { ActorRenderMode } from '../../../types/index.js'; import getClosestImageId from '../../../utilities/getClosestImageId.js'; import imageIdToURI from '../../../utilities/imageIdToURI.js'; import isEqual from '../../../utilities/isEqual.js'; import { getDimensionGroupReferenceContext, } from '../genericViewportReferenceCompatibility.js'; import { getVolumeImageIdIndexWorldPoint } from './planarSliceBasis.js'; import { getPlanarReferencedImageId, getPlanarViewReference, getPlanarViewReferenceId, isPlanarPlaneViewable, } from './planarViewReference.js'; import { resolvePlanarViewportView } from './PlanarResolvedView.js'; class PlanarViewReferenceController { constructor(host) { this.host = host; } getVolumeId(viewRefSpecifier = {}) { return (this.getPlanarReferenceContext(viewRefSpecifier)?.data.volumeId ?? this.getCurrentPlanarReferenceContext()?.data.volumeId); } getCurrentImageId(viewRefSpecifier = {}) { const referenceContext = this.getPlanarReferenceContext(viewRefSpecifier); return getPlanarReferencedImageId({ viewState: this.host.getViewState(), data: referenceContext?.data, frameOfReferenceUID: referenceContext?.frameOfReferenceUID, rendering: referenceContext?.rendering, renderContext: this.host.getRenderContext(), viewRefSpecifier, }); } getViewReference(viewRefSpecifier = {}) { const referenceContext = this.getPlanarReferenceContext(viewRefSpecifier); return getPlanarViewReference({ viewState: this.host.getViewState(), dataId: referenceContext?.dataId, frameOfReferenceUID: referenceContext?.frameOfReferenceUID ?? this.getFrameOfReferenceUID(), data: referenceContext?.data, rendering: referenceContext?.rendering, renderContext: this.host.getRenderContext(), viewRefSpecifier, }); } getViewReferenceId(viewRefSpecifier = {}) { const referenceContext = this.getPlanarReferenceContext(viewRefSpecifier); return getPlanarViewReferenceId({ viewState: this.host.getViewState(), frameOfReferenceUID: referenceContext?.frameOfReferenceUID, data: referenceContext?.data, rendering: referenceContext?.rendering, renderContext: this.host.getRenderContext(), viewRefSpecifier, }); } setViewReference(viewRef) { if (!viewRef) { return; } const targetContext = this.resolveViewReferenceContext(viewRef); if (!targetContext) { return; } const didActivateBinding = this.activatePlanarReferenceContext(targetContext.dataId); const didApplyReference = this.applyViewReferenceToCurrentBinding(viewRef); if (didActivateBinding && !didApplyReference) { this.host.render(); } } getFrameOfReferenceUID() { const frameOfReferenceUID = this.resolveFrameOfReferenceUID(); return (this.host .getResolvedView({ frameOfReferenceUID, }) ?.getFrameOfReferenceUID() ?? frameOfReferenceUID); } resolveFrameOfReferenceUID() { const binding = this.host.getCurrentBinding(); return (binding?.getFrameOfReferenceUID() ?? `${this.host.viewportType}-viewport-${this.host.viewportId}`); } isPlaneViewable(planeRestriction, options) { return isPlanarPlaneViewable({ viewState: this.host.getViewState(), frameOfReferenceUID: this.getFrameOfReferenceUID(), options, planeRestriction, rendering: this.getCurrentPlanarRendering(), renderContext: this.host.getRenderContext(), }); } getReferenceViewContexts(fallbackContexts) { const referenceContexts = this.getAllPlanarReferenceContexts(); if (!referenceContexts.length) { return fallbackContexts; } return referenceContexts.map((referenceContext) => this.getGenericViewportReferenceContext(referenceContext)); } getPlanarReferenceContext(viewRefSpecifier = {}) { if (viewRefSpecifier.volumeId) { return (this.findPlanarReferenceContextByVolumeId(viewRefSpecifier.volumeId) ?? this.getCurrentPlanarReferenceContext()); } return this.getCurrentPlanarReferenceContext(); } getCurrentPlanarReferenceContext() { const activeDataId = this.host.getActiveDataId(); if (activeDataId) { const activeContext = this.getPlanarReferenceContextByDataId(activeDataId); if (activeContext) { return activeContext; } } for (const [dataId] of this.host.getBindings()) { return this.getPlanarReferenceContextByDataId(dataId); } } getPlanarReferenceContextByDataId(dataId) { const binding = this.host.getBinding(dataId); if (!binding) { return; } return { dataId, data: binding.data, frameOfReferenceUID: binding.getFrameOfReferenceUID() ?? `${this.host.viewportType}-viewport-${this.host.viewportId}`, rendering: binding.rendering, }; } getAllPlanarReferenceContexts() { const contexts = []; for (const [dataId] of this.host.getBindings()) { const referenceContext = this.getPlanarReferenceContextByDataId(dataId); if (referenceContext) { contexts.push(referenceContext); } } return contexts; } getGenericViewportReferenceContext(referenceContext) { const resolvedView = resolvePlanarViewportView({ viewState: this.host.getViewState(), data: referenceContext.data, frameOfReferenceUID: referenceContext.frameOfReferenceUID, rendering: referenceContext.rendering, renderContext: this.host.getRenderContext(), })?.toICamera(); const volumeId = referenceContext.data.volumeId; return { dataId: referenceContext.dataId, dataIds: [referenceContext.data.id], frameOfReferenceUID: referenceContext.frameOfReferenceUID, imageIds: this.getImageIdsForReferenceContext(referenceContext), currentImageIdIndex: referenceContext.rendering.currentImageIdIndex, volumeId, volumeIds: volumeId ? [volumeId] : undefined, cameraFocalPoint: resolvedView?.focalPoint, viewPlaneNormal: resolvedView?.viewPlaneNormal, ...getDimensionGroupReferenceContext(referenceContext.data.imageVolume), }; } findPlanarReferenceContextByVolumeId(volumeId) { for (const [dataId, binding] of this.host.getBindings()) { const bindingData = binding.data; if (bindingData.volumeId === volumeId) { return this.getPlanarReferenceContextByDataId(dataId); } } } findPlanarReferenceContextByImageReference(referencedImageId, referencedImageURI) { const targetImageURI = referencedImageURI || (referencedImageId ? imageIdToURI(referencedImageId) : undefined); if (!targetImageURI) { return; } for (const referenceContext of this.getAllPlanarReferenceContexts()) { if (this.getImageIdsForReferenceContext(referenceContext).some((imageId) => imageIdToURI(imageId) === targetImageURI)) { return referenceContext; } } } resolveViewReferenceContext(viewRef) { if (viewRef.dataId) { const dataContext = this.getPlanarReferenceContextByDataId(viewRef.dataId); if (dataContext) { return dataContext; } } if (viewRef.volumeId) { const volumeContext = this.findPlanarReferenceContextByVolumeId(viewRef.volumeId); if (volumeContext) { return volumeContext; } } const imageContext = this.findPlanarReferenceContextByImageReference(viewRef.referencedImageId, viewRef.referencedImageURI); if (imageContext) { return imageContext; } const currentReferenceContext = this.getCurrentPlanarReferenceContext(); if (viewRef.FrameOfReferenceUID && currentReferenceContext?.frameOfReferenceUID !== viewRef.FrameOfReferenceUID) { return; } return currentReferenceContext; } activatePlanarReferenceContext(dataId) { if (this.host.getActiveDataId() === dataId) { return false; } this.host.promoteSourceDataId(dataId); this.host.updateBindingsCameraState(); return true; } applyViewReferenceToCurrentBinding(viewRef) { const referenceContext = this.getCurrentPlanarReferenceContext(); if (!referenceContext || (viewRef.FrameOfReferenceUID && referenceContext.frameOfReferenceUID !== viewRef.FrameOfReferenceUID)) { return false; } const { rendering } = referenceContext; if (rendering.renderMode === ActorRenderMode.CPU_IMAGE || rendering.renderMode === ActorRenderMode.VTK_IMAGE) { return this.applyImageViewReference(referenceContext, viewRef); } return this.applyVolumeViewReference(referenceContext, viewRef); } applyImageViewReference(referenceContext, viewRef) { const imageIds = this.getImageIdsForReferenceContext(referenceContext); const referencedImageIndex = this.findImageIdIndexByReference(imageIds, viewRef.referencedImageId, viewRef.referencedImageURI); if (typeof referencedImageIndex === 'number') { this.host.setImageIdIndex(referencedImageIndex); return true; } if (typeof viewRef.sliceIndex === 'number') { this.host.setImageIdIndex(viewRef.sliceIndex); return true; } return false; } applyVolumeViewReference(referenceContext, viewRef) { const normalizedViewRef = this.normalizeVolumeViewReference(referenceContext, viewRef); const resolvedView = this.host .getResolvedView({ frameOfReferenceUID: referenceContext.frameOfReferenceUID, }) ?.toICamera(); if (!resolvedView?.viewPlaneNormal) { return false; } const currentViewPlaneNormal = resolvedView.viewPlaneNormal; const refViewPlaneNormal = normalizedViewRef.viewPlaneNormal; const shouldReorient = refViewPlaneNormal && !this.areNormalsEqual(currentViewPlaneNormal, refViewPlaneNormal) && !this.areNormalsOpposite(currentViewPlaneNormal, refViewPlaneNormal); const effectiveViewPlaneNormal = refViewPlaneNormal ?? currentViewPlaneNormal; const isOppositeNormal = !shouldReorient && this.areNormalsOpposite(currentViewPlaneNormal, effectiveViewPlaneNormal); const sliceTarget = this.resolveVolumeReferenceSliceTarget(referenceContext, normalizedViewRef, effectiveViewPlaneNormal, isOppositeNormal); const viewStatePatch = {}; if (shouldReorient && refViewPlaneNormal) { viewStatePatch.orientation = { viewPlaneNormal: [...refViewPlaneNormal], ...(normalizedViewRef.viewUp ? { viewUp: [...normalizedViewRef.viewUp], } : {}), }; } const sliceWorldPoint = normalizedViewRef.cameraFocalPoint ?? sliceTarget?.sliceWorldPoint ?? (typeof sliceTarget?.imageIdIndex === 'number' ? this.host.getVolumeSliceWorldPointForImageIdIndex(sliceTarget.imageIdIndex) : undefined); if (sliceWorldPoint) { viewStatePatch.slice = { kind: 'volumePoint', sliceWorldPoint: [...sliceWorldPoint], }; } if (!Object.keys(viewStatePatch).length) { return false; } this.host.setViewState(viewStatePatch); return true; } normalizeVolumeViewReference(referenceContext, viewRef) { if (!viewRef.planeRestriction || viewRef.viewPlaneNormal) { return viewRef; } const orientation = this.deriveOrientationFromPlaneRestriction(referenceContext, viewRef.planeRestriction); return { ...viewRef, cameraFocalPoint: viewRef.cameraFocalPoint ?? viewRef.planeRestriction.point, ...orientation, }; } deriveOrientationFromPlaneRestriction(referenceContext, planeRestriction) { const resolvedView = this.host .getResolvedView({ frameOfReferenceUID: referenceContext.frameOfReferenceUID, }) ?.toICamera(); const currentViewPlaneNormal = resolvedView?.viewPlaneNormal; const currentViewUp = resolvedView?.viewUp; const { inPlaneVector1, inPlaneVector2 } = planeRestriction; const result = {}; if (currentViewPlaneNormal && this.isPlaneRestrictionCompatibleWithNormal(planeRestriction, currentViewPlaneNormal)) { result.viewPlaneNormal = [...currentViewPlaneNormal]; if (inPlaneVector1) { result.viewUp = [...inPlaneVector1]; } else if (currentViewUp) { result.viewUp = [...currentViewUp]; } return result; } let derivedViewPlaneNormal; if (inPlaneVector1 && inPlaneVector2) { const cross = vec3.cross(vec3.create(), inPlaneVector2, inPlaneVector1); if (vec3.length(cross) > 0) { vec3.normalize(cross, cross); derivedViewPlaneNormal = [...cross]; } } else if (inPlaneVector1 && currentViewPlaneNormal) { const cross = vec3.cross(vec3.create(), currentViewPlaneNormal, inPlaneVector1); if (vec3.length(cross) > 0) { vec3.normalize(cross, cross); derivedViewPlaneNormal = [...cross]; } } if (derivedViewPlaneNormal) { result.viewPlaneNormal = derivedViewPlaneNormal; } if (inPlaneVector1) { result.viewUp = [...inPlaneVector1]; } else if (currentViewUp) { result.viewUp = [...currentViewUp]; } return result; } isPlaneRestrictionCompatibleWithNormal(planeRestriction, viewPlaneNormal) { return ((!planeRestriction.inPlaneVector1 || isEqual(0, vec3.dot(viewPlaneNormal, planeRestriction.inPlaneVector1))) && (!planeRestriction.inPlaneVector2 || isEqual(0, vec3.dot(viewPlaneNormal, planeRestriction.inPlaneVector2)))); } resolveVolumeReferenceSliceTarget(referenceContext, viewRef, effectiveViewPlaneNormal, isOppositeNormal) { const { rendering } = referenceContext; if (rendering.renderMode !== ActorRenderMode.CPU_VOLUME && rendering.renderMode !== ActorRenderMode.VTK_VOLUME_SLICE) { return; } const imageIds = this.getImageIdsForReferenceContext(referenceContext); const maxImageIdIndex = rendering.maxImageIdIndex; if (typeof viewRef.sliceIndex === 'number' && viewRef.volumeId === referenceContext.data.volumeId && viewRef.viewPlaneNormal) { const targetSliceIndex = isOppositeNormal ? maxImageIdIndex - viewRef.sliceIndex : viewRef.sliceIndex; return { imageIdIndex: Math.min(Math.max(0, targetSliceIndex), maxImageIdIndex), }; } const referencedImageIndex = this.findImageIdIndexByReference(imageIds, viewRef.referencedImageId, viewRef.referencedImageURI); if (typeof referencedImageIndex === 'number') { return this.toVolumeSliceTarget(rendering.imageVolume, referencedImageIndex); } const targetPoint = viewRef.cameraFocalPoint ?? viewRef.planeRestriction?.point; if (targetPoint) { const referencedImageId = getClosestImageId(rendering.imageVolume, targetPoint, effectiveViewPlaneNormal); const closestImageIndex = this.findImageIdIndexByReference(imageIds, referencedImageId); if (typeof closestImageIndex === 'number') { return this.toVolumeSliceTarget(rendering.imageVolume, closestImageIndex); } return; } if (typeof viewRef.sliceIndex === 'number') { return { imageIdIndex: Math.min(Math.max(0, viewRef.sliceIndex), maxImageIdIndex), }; } } toVolumeSliceTarget(imageVolume, imageIdListIndex) { const sliceWorldPoint = getVolumeImageIdIndexWorldPoint(imageVolume, imageIdListIndex); return sliceWorldPoint ? { sliceWorldPoint } : { imageIdIndex: imageIdListIndex }; } getImageIdsForReferenceContext(referenceContext) { return (referenceContext.data.imageVolume?.imageIds || referenceContext.data.imageIds); } findImageIdIndexByReference(imageIds, referencedImageId, referencedImageURI) { const targetImageURI = referencedImageURI || (referencedImageId ? imageIdToURI(referencedImageId) : undefined); if (!targetImageURI) { return; } const foundImageIdIndex = imageIds.findIndex((imageId) => imageIdToURI(imageId) === targetImageURI); return foundImageIdIndex >= 0 ? foundImageIdIndex : undefined; } getCurrentPlanarRendering() { return this.host.getCurrentBinding()?.rendering; } areNormalsEqual(currentViewPlaneNormal, targetViewPlaneNormal) { return Boolean(currentViewPlaneNormal && targetViewPlaneNormal && isEqual(currentViewPlaneNormal, targetViewPlaneNormal)); } areNormalsOpposite(currentViewPlaneNormal, targetViewPlaneNormal) { if (!currentViewPlaneNormal || !targetViewPlaneNormal) { return false; } return isEqual(vec3.negate(vec3.create(), currentViewPlaneNormal), targetViewPlaneNormal); } } export default PlanarViewReferenceController;