@cornerstonejs/core
Version:
Cornerstone3D Core
423 lines (422 loc) • 18.7 kB
JavaScript
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;