@cornerstonejs/core 0.36.2 → 0.36.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/loaders/volumeLoader.d.ts +1 -0
- package/dist/cjs/loaders/volumeLoader.js +5 -1
- package/dist/cjs/loaders/volumeLoader.js.map +1 -1
- package/dist/cjs/utilities/getSliceRange.js +3 -1
- package/dist/cjs/utilities/getSliceRange.js.map +1 -1
- package/dist/cjs/utilities/getTargetVolumeAndSpacingInNormalDir.js +7 -0
- package/dist/cjs/utilities/getTargetVolumeAndSpacingInNormalDir.js.map +1 -1
- package/dist/esm/loaders/volumeLoader.d.ts +1 -0
- package/dist/esm/loaders/volumeLoader.js +3 -0
- package/dist/esm/loaders/volumeLoader.js.map +1 -1
- package/dist/esm/utilities/getSliceRange.js +3 -1
- package/dist/esm/utilities/getSliceRange.js.map +1 -1
- package/dist/esm/utilities/getTargetVolumeAndSpacingInNormalDir.js +7 -0
- package/dist/esm/utilities/getTargetVolumeAndSpacingInNormalDir.js.map +1 -1
- package/dist/umd/index.js +1 -1
- package/dist/umd/index.js.map +1 -1
- package/package.json +4 -3
- package/src/RenderingEngine/BaseVolumeViewport.ts +847 -0
- package/src/RenderingEngine/RenderingEngine.ts +1364 -0
- package/src/RenderingEngine/StackViewport.ts +2690 -0
- package/src/RenderingEngine/Viewport.ts +1244 -0
- package/src/RenderingEngine/VolumeViewport.ts +420 -0
- package/src/RenderingEngine/VolumeViewport3D.ts +42 -0
- package/src/RenderingEngine/getRenderingEngine.ts +34 -0
- package/src/RenderingEngine/helpers/addVolumesToViewports.ts +52 -0
- package/src/RenderingEngine/helpers/cpuFallback/colors/colormap.ts +343 -0
- package/src/RenderingEngine/helpers/cpuFallback/colors/index.ts +4 -0
- package/src/RenderingEngine/helpers/cpuFallback/colors/lookupTable.ts +469 -0
- package/src/RenderingEngine/helpers/cpuFallback/drawImageSync.ts +58 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/calculateTransform.ts +136 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/canvasToPixel.ts +25 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/computeAutoVoi.ts +47 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/correctShift.ts +38 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/createViewport.ts +64 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/doesImageNeedToBeRendered.ts +36 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/fitToWindow.ts +22 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/generateColorLUT.ts +60 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/generateLut.ts +83 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/getDefaultViewport.ts +88 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/getImageFitScale.ts +52 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/getImageSize.ts +55 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/getLut.ts +53 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/getModalityLut.ts +55 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/getTransform.ts +17 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/getVOILut.ts +74 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/initializeRenderCanvas.ts +37 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/lutMatches.ts +21 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/now.ts +13 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/pixelToCanvas.ts +22 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/renderColorImage.ts +193 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/renderGrayscaleImage.ts +166 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/renderPseudoColorImage.ts +203 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/resetCamera.ts +32 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/resize.ts +109 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/saveLastRendered.ts +36 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/setDefaultViewport.ts +17 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/setToPixelCoordinateSystem.ts +32 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/storedColorPixelDataToCanvasImageData.ts +58 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/storedPixelDataToCanvasImageData.ts +76 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/storedPixelDataToCanvasImageDataColorLUT.ts +60 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/storedPixelDataToCanvasImageDataPET.ts +50 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/storedPixelDataToCanvasImageDataPseudocolorLUT.ts +66 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/storedPixelDataToCanvasImageDataPseudocolorLUTPET.ts +68 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/storedPixelDataToCanvasImageDataRGBA.ts +81 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/storedRGBAPixelDataToCanvasImageData.ts +56 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/transform.ts +126 -0
- package/src/RenderingEngine/helpers/cpuFallback/rendering/validator.ts +31 -0
- package/src/RenderingEngine/helpers/createVolumeActor.ts +103 -0
- package/src/RenderingEngine/helpers/createVolumeMapper.ts +37 -0
- package/src/RenderingEngine/helpers/getOrCreateCanvas.ts +58 -0
- package/src/RenderingEngine/helpers/index.ts +15 -0
- package/src/RenderingEngine/helpers/isRgbaSourceRgbDest.ts +1 -0
- package/src/RenderingEngine/helpers/setDefaultVolumeVOI.ts +227 -0
- package/src/RenderingEngine/helpers/setVolumesForViewports.ts +52 -0
- package/src/RenderingEngine/helpers/viewportTypeToViewportClass.ts +14 -0
- package/src/RenderingEngine/helpers/viewportTypeUsesCustomRenderingPipeline.ts +7 -0
- package/src/RenderingEngine/helpers/volumeNewImageEventDispatcher.ts +75 -0
- package/src/RenderingEngine/index.ts +23 -0
- package/src/RenderingEngine/renderingEngineCache.ts +43 -0
- package/src/RenderingEngine/vtkClasses/index.js +11 -0
- package/src/RenderingEngine/vtkClasses/vtkOffscreenMultiRenderWindow.js +149 -0
- package/src/RenderingEngine/vtkClasses/vtkSharedVolumeMapper.js +52 -0
- package/src/RenderingEngine/vtkClasses/vtkSlabCamera.d.ts +781 -0
- package/src/RenderingEngine/vtkClasses/vtkSlabCamera.js +155 -0
- package/src/RenderingEngine/vtkClasses/vtkStreamingOpenGLRenderWindow.js +47 -0
- package/src/RenderingEngine/vtkClasses/vtkStreamingOpenGLTexture.js +272 -0
- package/src/RenderingEngine/vtkClasses/vtkStreamingOpenGLViewNodeFactory.js +159 -0
- package/src/RenderingEngine/vtkClasses/vtkStreamingOpenGLVolumeMapper.js +319 -0
- package/src/Settings.ts +294 -0
- package/src/cache/cache.ts +854 -0
- package/src/cache/classes/Contour.ts +70 -0
- package/src/cache/classes/ContourSet.ts +151 -0
- package/src/cache/classes/ImageVolume.ts +155 -0
- package/src/cache/index.ts +5 -0
- package/src/constants/cpuColormaps.ts +1537 -0
- package/src/constants/epsilon.ts +3 -0
- package/src/constants/index.ts +13 -0
- package/src/constants/mprCameraValues.ts +20 -0
- package/src/constants/rendering.ts +8 -0
- package/src/constants/viewportPresets.ts +357 -0
- package/src/enums/BlendModes.ts +23 -0
- package/src/enums/ContourType.ts +6 -0
- package/src/enums/Events.ts +196 -0
- package/src/enums/GeometryType.ts +5 -0
- package/src/enums/InterpolationType.ts +13 -0
- package/src/enums/OrientationAxis.ts +8 -0
- package/src/enums/RequestType.ts +13 -0
- package/src/enums/SharedArrayBufferModes.ts +11 -0
- package/src/enums/VOILUTFunctionType.ts +10 -0
- package/src/enums/ViewportType.ts +21 -0
- package/src/enums/index.ts +23 -0
- package/src/eventTarget.ts +67 -0
- package/src/getEnabledElement.ts +105 -0
- package/src/global.ts +8 -0
- package/src/index.ts +123 -0
- package/src/init.ts +247 -0
- package/src/loaders/geometryLoader.ts +108 -0
- package/src/loaders/imageLoader.ts +298 -0
- package/src/loaders/volumeLoader.ts +477 -0
- package/src/metaData.ts +84 -0
- package/src/requestPool/imageLoadPoolManager.ts +43 -0
- package/src/requestPool/imageRetrievalPoolManager.ts +25 -0
- package/src/requestPool/requestPoolManager.ts +329 -0
- package/src/types/ActorSliceRange.ts +17 -0
- package/src/types/CPUFallbackColormap.ts +23 -0
- package/src/types/CPUFallbackColormapData.ts +12 -0
- package/src/types/CPUFallbackColormapsData.ts +7 -0
- package/src/types/CPUFallbackEnabledElement.ts +71 -0
- package/src/types/CPUFallbackLUT.ts +5 -0
- package/src/types/CPUFallbackLookupTable.ts +17 -0
- package/src/types/CPUFallbackRenderingTools.ts +25 -0
- package/src/types/CPUFallbackTransform.ts +16 -0
- package/src/types/CPUFallbackViewport.ts +29 -0
- package/src/types/CPUFallbackViewportDisplayedArea.ts +15 -0
- package/src/types/CPUIImageData.ts +47 -0
- package/src/types/ContourData.ts +19 -0
- package/src/types/Cornerstone3DConfig.ts +31 -0
- package/src/types/CustomEventType.ts +14 -0
- package/src/types/EventTypes.ts +403 -0
- package/src/types/FlipDirection.ts +9 -0
- package/src/types/IActor.ts +23 -0
- package/src/types/ICache.ts +28 -0
- package/src/types/ICachedGeometry.ts +13 -0
- package/src/types/ICachedImage.ts +13 -0
- package/src/types/ICachedVolume.ts +12 -0
- package/src/types/ICamera.ts +36 -0
- package/src/types/IContour.ts +18 -0
- package/src/types/IContourSet.ts +56 -0
- package/src/types/IDynamicImageVolume.ts +18 -0
- package/src/types/IEnabledElement.ts +21 -0
- package/src/types/IGeometry.ts +12 -0
- package/src/types/IImage.ts +113 -0
- package/src/types/IImageData.ts +45 -0
- package/src/types/IImageVolume.ts +78 -0
- package/src/types/ILoadObject.ts +36 -0
- package/src/types/IRegisterImageLoader.ts +10 -0
- package/src/types/IRenderingEngine.ts +28 -0
- package/src/types/IStackViewport.ts +138 -0
- package/src/types/IStreamingImageVolume.ts +13 -0
- package/src/types/IStreamingVolumeProperties.ts +14 -0
- package/src/types/IViewport.ts +149 -0
- package/src/types/IViewportId.ts +9 -0
- package/src/types/IVolume.ts +45 -0
- package/src/types/IVolumeInput.ts +36 -0
- package/src/types/IVolumeViewport.ts +141 -0
- package/src/types/ImageLoaderFn.ts +16 -0
- package/src/types/ImageSliceData.ts +6 -0
- package/src/types/Mat3.ts +16 -0
- package/src/types/Metadata.ts +39 -0
- package/src/types/OrientationVectors.ts +36 -0
- package/src/types/Plane.ts +6 -0
- package/src/types/Point2.ts +6 -0
- package/src/types/Point3.ts +6 -0
- package/src/types/Point4.ts +6 -0
- package/src/types/ScalingParameters.ts +27 -0
- package/src/types/StackViewportProperties.ts +25 -0
- package/src/types/TransformMatrix2D.ts +4 -0
- package/src/types/ViewportInputOptions.ts +21 -0
- package/src/types/ViewportPreset.ts +14 -0
- package/src/types/VolumeLoaderFn.ts +18 -0
- package/src/types/VolumeViewportProperties.ts +14 -0
- package/src/types/index.ts +157 -0
- package/src/types/voi.ts +15 -0
- package/src/utilities/actorCheck.ts +24 -0
- package/src/utilities/applyPreset.ts +132 -0
- package/src/utilities/calculateViewportsSpatialRegistration.ts +74 -0
- package/src/utilities/calibratedPixelSpacingMetadataProvider.ts +38 -0
- package/src/utilities/createFloat32SharedArray.ts +45 -0
- package/src/utilities/createInt16SharedArray.ts +43 -0
- package/src/utilities/createLinearRGBTransferFunction.ts +22 -0
- package/src/utilities/createSigmoidRGBTransferFunction.ts +63 -0
- package/src/utilities/createUInt16SharedArray.ts +43 -0
- package/src/utilities/createUint8SharedArray.ts +45 -0
- package/src/utilities/deepFreeze.ts +19 -0
- package/src/utilities/deepMerge.ts +81 -0
- package/src/utilities/getClosestImageId.ts +80 -0
- package/src/utilities/getClosestStackImageIndexForPoint.ts +116 -0
- package/src/utilities/getImageSliceDataForVolumeViewport.ts +61 -0
- package/src/utilities/getMinMax.ts +31 -0
- package/src/utilities/getRuntimeId.ts +54 -0
- package/src/utilities/getScalarDataType.ts +31 -0
- package/src/utilities/getScalingParameters.ts +35 -0
- package/src/utilities/getSliceRange.ts +86 -0
- package/src/utilities/getSpacingInNormalDirection.ts +44 -0
- package/src/utilities/getTargetVolumeAndSpacingInNormalDir.ts +126 -0
- package/src/utilities/getViewportImageCornersInWorld.ts +102 -0
- package/src/utilities/getViewportsWithImageURI.ts +46 -0
- package/src/utilities/getViewportsWithVolumeId.ts +38 -0
- package/src/utilities/getVoiFromSigmoidRGBTransferFunction.ts +23 -0
- package/src/utilities/getVolumeActorCorners.ts +24 -0
- package/src/utilities/getVolumeSliceRangeInfo.ts +52 -0
- package/src/utilities/getVolumeViewportScrollInfo.ts +32 -0
- package/src/utilities/getVolumeViewportsContainingSameVolumes.ts +58 -0
- package/src/utilities/hasNaNValues.ts +12 -0
- package/src/utilities/imageIdToURI.ts +10 -0
- package/src/utilities/imageToWorldCoords.ts +54 -0
- package/src/utilities/index.ts +100 -0
- package/src/utilities/indexWithinDimensions.ts +27 -0
- package/src/utilities/invertRgbTransferFunction.ts +36 -0
- package/src/utilities/isEqual.ts +27 -0
- package/src/utilities/isOpposite.ts +23 -0
- package/src/utilities/isTypedArray.ts +20 -0
- package/src/utilities/loadImageToCanvas.ts +80 -0
- package/src/utilities/planar.ts +91 -0
- package/src/utilities/renderToCanvas.ts +32 -0
- package/src/utilities/scaleRgbTransferFunction.ts +37 -0
- package/src/utilities/snapFocalPointToSlice.ts +78 -0
- package/src/utilities/spatialRegistrationMetadataProvider.ts +50 -0
- package/src/utilities/transformWorldToIndex.ts +16 -0
- package/src/utilities/triggerEvent.ts +38 -0
- package/src/utilities/uuidv4.ts +13 -0
- package/src/utilities/windowLevel.ts +39 -0
- package/src/utilities/worldToImageCoords.ts +64 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { vec3 } from 'gl-matrix';
|
|
2
|
+
import * as metaData from '../metaData';
|
|
3
|
+
import type { IImageVolume, Point3 } from '../types';
|
|
4
|
+
|
|
5
|
+
import getSpacingInNormalDirection from './getSpacingInNormalDirection';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Given an image, a point in space and the viewPlaneNormal it returns the
|
|
9
|
+
* closest imageId of the image volume that is within half voxel spacing
|
|
10
|
+
* of the point in space.
|
|
11
|
+
* @param imageVolume - The image volume
|
|
12
|
+
* @param worldPos - The position in the world coordinate system (from mouse click)
|
|
13
|
+
* @param viewPlaneNormal - The normal vector of the viewport
|
|
14
|
+
* @param viewUp - The viewUp vector of the camera.
|
|
15
|
+
*
|
|
16
|
+
* @returns The imageId for the tool.
|
|
17
|
+
*/
|
|
18
|
+
export default function getClosestImageId(
|
|
19
|
+
imageVolume: IImageVolume,
|
|
20
|
+
worldPos: Point3,
|
|
21
|
+
viewPlaneNormal: Point3,
|
|
22
|
+
viewUp: Point3
|
|
23
|
+
): string {
|
|
24
|
+
if (!imageVolume) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const { direction, imageIds } = imageVolume;
|
|
29
|
+
|
|
30
|
+
if (!imageIds || !imageIds.length) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 1. Get ScanAxis vector
|
|
35
|
+
const kVector = direction.slice(6, 9);
|
|
36
|
+
|
|
37
|
+
// 2. Check if scanAxis is not parallel to camera viewPlaneNormal
|
|
38
|
+
const dotProducts = vec3.dot(kVector as Point3, <vec3>viewPlaneNormal);
|
|
39
|
+
|
|
40
|
+
// 2.a if imagePlane is not parallel to the camera: tool is not drawn on an
|
|
41
|
+
// imaging plane, return
|
|
42
|
+
if (Math.abs(dotProducts) < 0.99) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 3. Calculate Spacing the in the normal direction, this will get used to
|
|
47
|
+
// check whether we are withing a slice
|
|
48
|
+
const spacingInNormalDirection = getSpacingInNormalDirection(
|
|
49
|
+
imageVolume,
|
|
50
|
+
viewPlaneNormal
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const halfSpacingInNormalDirection = spacingInNormalDirection / 2;
|
|
54
|
+
|
|
55
|
+
// 4. Iterate over all imageIds and check if the tool point (worldPos) is
|
|
56
|
+
// withing one of the slices defined by an imageId
|
|
57
|
+
let imageIdForTool;
|
|
58
|
+
for (let i = 0; i < imageIds.length; i++) {
|
|
59
|
+
const imageId = imageIds[i];
|
|
60
|
+
|
|
61
|
+
// 4.a Get metadata for the imageId
|
|
62
|
+
const { imagePositionPatient } = metaData.get('imagePlaneModule', imageId);
|
|
63
|
+
|
|
64
|
+
// 4.b Calculate the direction vector from annotation. point to the first voxel
|
|
65
|
+
// of this image defined by imageId
|
|
66
|
+
const dir = vec3.create();
|
|
67
|
+
vec3.sub(dir, worldPos, imagePositionPatient);
|
|
68
|
+
|
|
69
|
+
// 4.c Calculate the distance between the vector above and the viewplaneNormal
|
|
70
|
+
// i.e., projected distance
|
|
71
|
+
const dot = vec3.dot(dir, viewPlaneNormal);
|
|
72
|
+
|
|
73
|
+
// 4.d If the distance is withing range, return the imageId
|
|
74
|
+
if (Math.abs(dot) < halfSpacingInNormalDirection) {
|
|
75
|
+
imageIdForTool = imageId;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return imageIdForTool;
|
|
80
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { vec3 } from 'gl-matrix';
|
|
2
|
+
import { planar } from '.';
|
|
3
|
+
import { metaData } from '..';
|
|
4
|
+
import { IStackViewport, Point3 } from '../types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Given a point in 3D space and a viewport it returns the index of the closest imageId, it assumes that stack images are sorted according to their sliceLocation
|
|
8
|
+
* @param point - [A, B, C] coordinates of the point in 3D space
|
|
9
|
+
* @param viewport - The StackViewport to search for the closest imageId
|
|
10
|
+
*
|
|
11
|
+
* @returns The imageId index of the closest imageId or null if no imageId is found
|
|
12
|
+
*/
|
|
13
|
+
export default function getClosestStackImageIndexForPoint(
|
|
14
|
+
point: Point3,
|
|
15
|
+
viewport: IStackViewport
|
|
16
|
+
): number | null {
|
|
17
|
+
const minimalDistance = calculateMinimalDistanceForStackViewport(
|
|
18
|
+
point,
|
|
19
|
+
viewport
|
|
20
|
+
);
|
|
21
|
+
return minimalDistance ? minimalDistance.index : null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
//assumes that imageIds are sorted by slice location
|
|
25
|
+
export function calculateMinimalDistanceForStackViewport(
|
|
26
|
+
point: Point3,
|
|
27
|
+
viewport: IStackViewport
|
|
28
|
+
): { distance: number; index: number } | null {
|
|
29
|
+
const imageIds = viewport.getImageIds();
|
|
30
|
+
const currentImageIdIndex = viewport.getCurrentImageIdIndex();
|
|
31
|
+
|
|
32
|
+
if (imageIds.length === 0) return null;
|
|
33
|
+
|
|
34
|
+
const getDistance = (imageId: string): null | number => {
|
|
35
|
+
const planeMetadata = getPlaneMetadata(imageId);
|
|
36
|
+
if (!planeMetadata) return null;
|
|
37
|
+
const plane = planar.planeEquation(
|
|
38
|
+
planeMetadata.planeNormal,
|
|
39
|
+
planeMetadata.imagePositionPatient
|
|
40
|
+
);
|
|
41
|
+
const distance = planar.planeDistanceToPoint(plane, point);
|
|
42
|
+
return distance;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const closestStack = {
|
|
46
|
+
distance: getDistance(imageIds[currentImageIdIndex]) ?? Infinity,
|
|
47
|
+
index: currentImageIdIndex,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
//check higher indices
|
|
51
|
+
const higherImageIds = imageIds.slice(currentImageIdIndex + 1);
|
|
52
|
+
|
|
53
|
+
for (let i = 0; i < higherImageIds.length; i++) {
|
|
54
|
+
const id = higherImageIds[i];
|
|
55
|
+
const distance = getDistance(id);
|
|
56
|
+
if (distance === null) continue;
|
|
57
|
+
if (distance <= closestStack.distance) {
|
|
58
|
+
closestStack.distance = distance;
|
|
59
|
+
closestStack.index = i + currentImageIdIndex + 1;
|
|
60
|
+
} else break;
|
|
61
|
+
}
|
|
62
|
+
//check lower indices
|
|
63
|
+
const lowerImageIds = imageIds.slice(0, currentImageIdIndex);
|
|
64
|
+
for (let i = lowerImageIds.length - 1; i >= 0; i--) {
|
|
65
|
+
const id = lowerImageIds[i];
|
|
66
|
+
const distance = getDistance(id);
|
|
67
|
+
if (distance === null || distance === closestStack.distance) continue;
|
|
68
|
+
if (distance < closestStack.distance) {
|
|
69
|
+
closestStack.distance = distance;
|
|
70
|
+
closestStack.index = i;
|
|
71
|
+
} else break;
|
|
72
|
+
}
|
|
73
|
+
return closestStack.distance === Infinity ? null : closestStack;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function getPlaneMetadata(imageId: string): null | {
|
|
77
|
+
rowCosines: Point3;
|
|
78
|
+
columnCosines: Point3;
|
|
79
|
+
imagePositionPatient: Point3;
|
|
80
|
+
planeNormal: Point3;
|
|
81
|
+
} {
|
|
82
|
+
const targetImagePlane = metaData.get('imagePlaneModule', imageId);
|
|
83
|
+
|
|
84
|
+
if (
|
|
85
|
+
!targetImagePlane ||
|
|
86
|
+
!(
|
|
87
|
+
targetImagePlane.rowCosines instanceof Array &&
|
|
88
|
+
targetImagePlane.rowCosines.length === 3
|
|
89
|
+
) ||
|
|
90
|
+
!(
|
|
91
|
+
targetImagePlane.columnCosines instanceof Array &&
|
|
92
|
+
targetImagePlane.columnCosines.length === 3
|
|
93
|
+
) ||
|
|
94
|
+
!(
|
|
95
|
+
targetImagePlane.imagePositionPatient instanceof Array &&
|
|
96
|
+
targetImagePlane.imagePositionPatient.length === 3
|
|
97
|
+
)
|
|
98
|
+
) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
const {
|
|
102
|
+
rowCosines,
|
|
103
|
+
columnCosines,
|
|
104
|
+
imagePositionPatient,
|
|
105
|
+
}: {
|
|
106
|
+
rowCosines: Point3;
|
|
107
|
+
columnCosines: Point3;
|
|
108
|
+
imagePositionPatient: Point3;
|
|
109
|
+
} = targetImagePlane;
|
|
110
|
+
|
|
111
|
+
const rowVec = vec3.set(vec3.create(), ...rowCosines);
|
|
112
|
+
const colVec = vec3.set(vec3.create(), ...columnCosines);
|
|
113
|
+
const planeNormal = vec3.cross(vec3.create(), rowVec, colVec) as Point3;
|
|
114
|
+
|
|
115
|
+
return { rowCosines, columnCosines, imagePositionPatient, planeNormal };
|
|
116
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { ImageSliceData, IVolumeViewport, VolumeActor } from '../types';
|
|
2
|
+
import getSliceRange from './getSliceRange';
|
|
3
|
+
import getTargetVolumeAndSpacingInNormalDir from './getTargetVolumeAndSpacingInNormalDir';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* It calculates the number of slices and the current slice index for a given
|
|
7
|
+
* Volume viewport
|
|
8
|
+
* @param viewport - volume viewport
|
|
9
|
+
* @returns An object with two properties: numberOfSlices and imageIndex.
|
|
10
|
+
*/
|
|
11
|
+
function getImageSliceDataForVolumeViewport(
|
|
12
|
+
viewport: IVolumeViewport
|
|
13
|
+
): ImageSliceData {
|
|
14
|
+
const camera = viewport.getCamera();
|
|
15
|
+
|
|
16
|
+
const { spacingInNormalDirection, imageVolume } =
|
|
17
|
+
getTargetVolumeAndSpacingInNormalDir(viewport, camera);
|
|
18
|
+
|
|
19
|
+
if (!imageVolume) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const { viewPlaneNormal, focalPoint } = camera;
|
|
24
|
+
|
|
25
|
+
const actorEntry = viewport
|
|
26
|
+
.getActors()
|
|
27
|
+
.find(
|
|
28
|
+
(a) =>
|
|
29
|
+
a.referenceId === imageVolume.volumeId || a.uid === imageVolume.volumeId
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
if (!actorEntry) {
|
|
33
|
+
console.warn('No actor found for with actorUID of', imageVolume.volumeId);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const volumeActor = actorEntry.actor as VolumeActor;
|
|
37
|
+
const sliceRange = getSliceRange(volumeActor, viewPlaneNormal, focalPoint);
|
|
38
|
+
|
|
39
|
+
const { min, max, current } = sliceRange;
|
|
40
|
+
|
|
41
|
+
// calculate number of steps from min to max with current normal spacing in direction
|
|
42
|
+
const numberOfSlices = Math.round((max - min) / spacingInNormalDirection) + 1;
|
|
43
|
+
|
|
44
|
+
// calculate the imageIndex based on min, max, current
|
|
45
|
+
let imageIndex = ((current - min) / (max - min)) * numberOfSlices;
|
|
46
|
+
imageIndex = Math.floor(imageIndex);
|
|
47
|
+
|
|
48
|
+
// Clamp imageIndex
|
|
49
|
+
if (imageIndex > numberOfSlices - 1) {
|
|
50
|
+
imageIndex = numberOfSlices - 1;
|
|
51
|
+
} else if (imageIndex < 0) {
|
|
52
|
+
imageIndex = 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
numberOfSlices,
|
|
57
|
+
imageIndex,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export default getImageSliceDataForVolumeViewport;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculate the minimum and maximum values in an Array
|
|
3
|
+
*
|
|
4
|
+
* @param storedPixelData - The pixel data to calculate the min and max values for
|
|
5
|
+
* @returns an object with two properties: min and max
|
|
6
|
+
*/
|
|
7
|
+
export default function getMinMax(storedPixelData: number[]): {
|
|
8
|
+
min: number;
|
|
9
|
+
max: number;
|
|
10
|
+
} {
|
|
11
|
+
// we always calculate the min max values since they are not always
|
|
12
|
+
// present in DICOM and we don't want to trust them anyway as cornerstone
|
|
13
|
+
// depends on us providing reliable values for these
|
|
14
|
+
let min = storedPixelData[0];
|
|
15
|
+
|
|
16
|
+
let max = storedPixelData[0];
|
|
17
|
+
|
|
18
|
+
let storedPixel;
|
|
19
|
+
const numPixels = storedPixelData.length;
|
|
20
|
+
|
|
21
|
+
for (let index = 1; index < numPixels; index++) {
|
|
22
|
+
storedPixel = storedPixelData[index];
|
|
23
|
+
min = Math.min(min, storedPixel);
|
|
24
|
+
max = Math.max(max, storedPixel);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
min,
|
|
29
|
+
max,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const LAST_RUNTIME_ID = Symbol('LastRuntimeId');
|
|
2
|
+
const GLOBAL_CONTEXT = {};
|
|
3
|
+
const DEFAULT_MAX = 0xffffffff; // Max 32-bit integer
|
|
4
|
+
const DEFAULT_SEPARATOR = '-';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Generate a unique numeric ID string valid during a single runtime session;
|
|
8
|
+
*
|
|
9
|
+
* @param context - An optional object to be used as context.
|
|
10
|
+
* Defaults to a global context;
|
|
11
|
+
* @param separator - The component separator. Defaults to "-";
|
|
12
|
+
* @param max - The maximum component value. Defaults to 4294967295;
|
|
13
|
+
* @returns The string representation of the the unique ID;
|
|
14
|
+
*/
|
|
15
|
+
export default function getRuntimeId(
|
|
16
|
+
context?: unknown,
|
|
17
|
+
separator?: string,
|
|
18
|
+
max?: number
|
|
19
|
+
): string {
|
|
20
|
+
return getNextRuntimeId(
|
|
21
|
+
// @ts-ignore
|
|
22
|
+
context !== null && typeof context === 'object' ? context : GLOBAL_CONTEXT,
|
|
23
|
+
LAST_RUNTIME_ID,
|
|
24
|
+
(typeof max === 'number' && max > 0 ? max : DEFAULT_MAX) >>> 0
|
|
25
|
+
).join(typeof separator === 'string' ? separator : DEFAULT_SEPARATOR);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/*
|
|
29
|
+
* Helpers
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
function getNextRuntimeId(
|
|
33
|
+
context: Record<symbol, Array<number>>,
|
|
34
|
+
symbol: symbol,
|
|
35
|
+
max: number
|
|
36
|
+
): Array<number> {
|
|
37
|
+
let idComponents = context[symbol];
|
|
38
|
+
if (!(idComponents instanceof Array)) {
|
|
39
|
+
idComponents = [0];
|
|
40
|
+
Object.defineProperty(context, symbol, { value: idComponents });
|
|
41
|
+
}
|
|
42
|
+
for (let carry = true, i = 0; carry && i < idComponents.length; ++i) {
|
|
43
|
+
let n = idComponents[i] | 0;
|
|
44
|
+
if (n < max) {
|
|
45
|
+
carry = false;
|
|
46
|
+
n = n + 1;
|
|
47
|
+
} else {
|
|
48
|
+
n = 0;
|
|
49
|
+
if (i + 1 === idComponents.length) idComponents.push(0);
|
|
50
|
+
}
|
|
51
|
+
idComponents[i] = n;
|
|
52
|
+
}
|
|
53
|
+
return idComponents;
|
|
54
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ScalingParameters } from '../types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* If the scalar data is a Uint8Array, return 'Uint8Array'. If the scalar data is a
|
|
5
|
+
* Float32Array, return 'Float32Array'. If the scalar data is a Int16Array, return
|
|
6
|
+
* 'Int16Array'. If the scalar data is a Uint16Array, return 'Uint16Array'. If the
|
|
7
|
+
* scalar data is none of the above, throw an error.
|
|
8
|
+
* @param {ScalingParameters} scalingParameters - {
|
|
9
|
+
* @param {any} [scalarData] - The data to be converted.
|
|
10
|
+
* @returns The data type of the scalar data.
|
|
11
|
+
*/
|
|
12
|
+
export default function getScalarDataType(
|
|
13
|
+
scalingParameters: ScalingParameters,
|
|
14
|
+
scalarData?: any
|
|
15
|
+
): string {
|
|
16
|
+
let type;
|
|
17
|
+
|
|
18
|
+
if (scalarData && scalarData instanceof Uint8Array) {
|
|
19
|
+
type = 'Uint8Array';
|
|
20
|
+
} else if (scalarData instanceof Float32Array) {
|
|
21
|
+
type = 'Float32Array';
|
|
22
|
+
} else if (scalarData instanceof Int16Array) {
|
|
23
|
+
type = 'Int16Array';
|
|
24
|
+
} else if (scalarData instanceof Uint16Array) {
|
|
25
|
+
type = 'Uint16Array';
|
|
26
|
+
} else {
|
|
27
|
+
throw new Error('Unsupported array type');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return type;
|
|
31
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { get as metaDataGet } from '../metaData';
|
|
2
|
+
import { ScalingParameters } from '../types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* It returns the scaling parameters for the image with the given imageId. This can be
|
|
6
|
+
* used to get passed (as an option) to the imageLoader in order to apply scaling to the image inside
|
|
7
|
+
* the imageLoader.
|
|
8
|
+
* @param imageId - The imageId of the image
|
|
9
|
+
* @returns ScalingParameters
|
|
10
|
+
*/
|
|
11
|
+
export default function getScalingParameters(
|
|
12
|
+
imageId: string
|
|
13
|
+
): ScalingParameters {
|
|
14
|
+
const modalityLutModule = metaDataGet('modalityLutModule', imageId) || {};
|
|
15
|
+
const generalSeriesModule = metaDataGet('generalSeriesModule', imageId) || {};
|
|
16
|
+
|
|
17
|
+
const { modality } = generalSeriesModule;
|
|
18
|
+
|
|
19
|
+
const scalingParameters = {
|
|
20
|
+
rescaleSlope: modalityLutModule.rescaleSlope,
|
|
21
|
+
rescaleIntercept: modalityLutModule.rescaleIntercept,
|
|
22
|
+
modality,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const suvFactor = metaDataGet('scalingModule', imageId) || {};
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
...scalingParameters,
|
|
29
|
+
...(modality === 'PT' && {
|
|
30
|
+
suvbw: suvFactor.suvbw,
|
|
31
|
+
suvbsa: suvFactor.suvbsa,
|
|
32
|
+
suvlbm: suvFactor.suvlbm,
|
|
33
|
+
}),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import vtkMatrixBuilder from '@kitware/vtk.js/Common/Core/MatrixBuilder';
|
|
2
|
+
import getVolumeActorCorners from './getVolumeActorCorners';
|
|
3
|
+
import type { VolumeActor, Point3, ActorSliceRange } from '../types';
|
|
4
|
+
import { EPSILON } from '../constants';
|
|
5
|
+
|
|
6
|
+
const SMALL_EPSILON = EPSILON * EPSILON;
|
|
7
|
+
const isOne = (v) => Math.abs(Math.abs(v) - 1) < SMALL_EPSILON;
|
|
8
|
+
const isUnit = (v, off) =>
|
|
9
|
+
isOne(v[off]) || isOne(v[off + 1]) || isOne(v[off + 2]);
|
|
10
|
+
|
|
11
|
+
const isOrthonormal = (v) => isUnit(v, 0) && isUnit(v, 3) && isUnit(v, 6);
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Given a `vtkVolumeActor`, and a normal direction,
|
|
15
|
+
* calculate the range of slices in the focal normal direction that encapsulate
|
|
16
|
+
* the volume. Also project the `focalPoint` onto this range.
|
|
17
|
+
*
|
|
18
|
+
* @param volumeActor - The `vtkVolumeActor`.
|
|
19
|
+
* @param viewPlaneNormal - The normal to the camera view.
|
|
20
|
+
* @param focalPoint - The focal point of the camera.
|
|
21
|
+
*
|
|
22
|
+
* @returns an object containing the `min`, `max` and `current`
|
|
23
|
+
* positions in the normal direction.
|
|
24
|
+
*/
|
|
25
|
+
export default function getSliceRange(
|
|
26
|
+
volumeActor: VolumeActor,
|
|
27
|
+
viewPlaneNormal: Point3,
|
|
28
|
+
focalPoint: Point3
|
|
29
|
+
): ActorSliceRange {
|
|
30
|
+
const imageData = volumeActor.getMapper().getInputData();
|
|
31
|
+
let corners;
|
|
32
|
+
const direction = imageData.getDirection();
|
|
33
|
+
|
|
34
|
+
if (isOrthonormal(direction)) {
|
|
35
|
+
// This logic is only valid when the IJK vectors are unit vectors
|
|
36
|
+
corners = getVolumeActorCorners(volumeActor);
|
|
37
|
+
} else {
|
|
38
|
+
// This logic works for both unit and non-unit vectors, but is slower
|
|
39
|
+
const [dx, dy, dz] = imageData.getDimensions();
|
|
40
|
+
const cornersIdx = [
|
|
41
|
+
[0, 0, 0],
|
|
42
|
+
[dx - 1, 0, 0],
|
|
43
|
+
[0, dy - 1, 0],
|
|
44
|
+
[dx - 1, dy - 1, 0],
|
|
45
|
+
[0, 0, dz - 1],
|
|
46
|
+
[dx - 1, 0, dz - 1],
|
|
47
|
+
[0, dy - 1, dz - 1],
|
|
48
|
+
[dx - 1, dy - 1, dz - 1],
|
|
49
|
+
];
|
|
50
|
+
corners = cornersIdx.map((it) => imageData.indexToWorld(it));
|
|
51
|
+
}
|
|
52
|
+
// Get rotation matrix from normal to +X (since bounds is aligned to XYZ)
|
|
53
|
+
const transform = vtkMatrixBuilder
|
|
54
|
+
.buildFromDegree()
|
|
55
|
+
.identity()
|
|
56
|
+
.rotateFromDirections(viewPlaneNormal, [1, 0, 0]);
|
|
57
|
+
|
|
58
|
+
corners.forEach((pt) => transform.apply(pt));
|
|
59
|
+
|
|
60
|
+
const transformedFocalPoint = [...focalPoint];
|
|
61
|
+
transform.apply(transformedFocalPoint);
|
|
62
|
+
|
|
63
|
+
const currentSlice = transformedFocalPoint[0];
|
|
64
|
+
|
|
65
|
+
// range is now maximum X distance
|
|
66
|
+
let minX = Infinity;
|
|
67
|
+
let maxX = -Infinity;
|
|
68
|
+
for (let i = 0; i < 8; i++) {
|
|
69
|
+
const x = corners[i][0];
|
|
70
|
+
if (x > maxX) {
|
|
71
|
+
maxX = x;
|
|
72
|
+
}
|
|
73
|
+
if (x < minX) {
|
|
74
|
+
minX = x;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
min: minX,
|
|
80
|
+
max: maxX,
|
|
81
|
+
current: currentSlice,
|
|
82
|
+
actor: volumeActor,
|
|
83
|
+
viewPlaneNormal,
|
|
84
|
+
focalPoint,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { vec3 } from 'gl-matrix';
|
|
2
|
+
import { IImageVolume, Point3 } from '../types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Given an `imageVolume` and a normal direction (`viewPlaneNormal`), calculates
|
|
6
|
+
* the spacing between voxels in the normal direction. If (`viewPlaneNormal`) is
|
|
7
|
+
* parallel to one of the directions you will obtain the spacing in that direction.
|
|
8
|
+
* Otherwise each of the `imageVolume`'s directions are projected onto the volume,
|
|
9
|
+
* so that you obtain a spacing of the order of "seeing a new set of voxels if the camera where to dolly".
|
|
10
|
+
*
|
|
11
|
+
* @param imageVolume - The image volume to calculate the spacing in the normal direction.
|
|
12
|
+
* @param viewPlaneNormal - The normal direction of the view plane.
|
|
13
|
+
* @returns
|
|
14
|
+
*/
|
|
15
|
+
export default function getSpacingInNormalDirection(
|
|
16
|
+
imageVolume: IImageVolume,
|
|
17
|
+
viewPlaneNormal: Point3
|
|
18
|
+
): number {
|
|
19
|
+
const { direction, spacing } = imageVolume;
|
|
20
|
+
|
|
21
|
+
// Calculate size of spacing vector in normal direction
|
|
22
|
+
const iVector = direction.slice(0, 3) as Point3;
|
|
23
|
+
const jVector = direction.slice(3, 6) as Point3;
|
|
24
|
+
const kVector = direction.slice(6, 9) as Point3;
|
|
25
|
+
|
|
26
|
+
const dotProducts = [
|
|
27
|
+
vec3.dot(iVector, <vec3>viewPlaneNormal),
|
|
28
|
+
vec3.dot(jVector, <vec3>viewPlaneNormal),
|
|
29
|
+
vec3.dot(kVector, <vec3>viewPlaneNormal),
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
const projectedSpacing = vec3.create();
|
|
33
|
+
|
|
34
|
+
vec3.set(
|
|
35
|
+
projectedSpacing,
|
|
36
|
+
dotProducts[0] * spacing[0],
|
|
37
|
+
dotProducts[1] * spacing[1],
|
|
38
|
+
dotProducts[2] * spacing[2]
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const spacingInNormalDirection = vec3.length(projectedSpacing);
|
|
42
|
+
|
|
43
|
+
return spacingInNormalDirection;
|
|
44
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import cache from '../cache/cache';
|
|
2
|
+
import { EPSILON } from '../constants';
|
|
3
|
+
// import type { VolumeViewport } from '../RenderingEngine'
|
|
4
|
+
import { ICamera, IImageVolume, IVolumeViewport } from '../types';
|
|
5
|
+
import getSpacingInNormalDirection from './getSpacingInNormalDirection';
|
|
6
|
+
import { getVolumeLoaderSchemes } from '../loaders/volumeLoader';
|
|
7
|
+
|
|
8
|
+
// One EPSILON part larger multiplier
|
|
9
|
+
const EPSILON_PART = 1 + EPSILON;
|
|
10
|
+
|
|
11
|
+
const startsWith = (str, starts) =>
|
|
12
|
+
starts === str.substring(0, Math.min(str.length, starts.length));
|
|
13
|
+
|
|
14
|
+
// Check if this is a primary volume
|
|
15
|
+
// For now, that means it came from some sort of image loader, but
|
|
16
|
+
// should be specifically designated.
|
|
17
|
+
const isPrimaryVolume = (volume): boolean =>
|
|
18
|
+
!!getVolumeLoaderSchemes().find((scheme) =>
|
|
19
|
+
startsWith(volume.volumeId, scheme)
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Given a volume viewport and camera, find the target volume.
|
|
24
|
+
* The imageVolume is retrieved from cache for the specified targetVolumeId or
|
|
25
|
+
* in case it is not provided, it chooses the volumeId on the viewport (there
|
|
26
|
+
* might be more than one in case of fusion) that has the finest resolution in the
|
|
27
|
+
* direction of view (normal).
|
|
28
|
+
*
|
|
29
|
+
* @param viewport - volume viewport
|
|
30
|
+
* @param camera - current camera
|
|
31
|
+
* @param targetVolumeId - If a target volumeId is given that volume
|
|
32
|
+
* is forced to be used.
|
|
33
|
+
*
|
|
34
|
+
* @returns An object containing the imageVolume and spacingInNormalDirection.
|
|
35
|
+
*
|
|
36
|
+
*/
|
|
37
|
+
export default function getTargetVolumeAndSpacingInNormalDir(
|
|
38
|
+
viewport: IVolumeViewport,
|
|
39
|
+
camera: ICamera,
|
|
40
|
+
targetVolumeId?: string
|
|
41
|
+
): {
|
|
42
|
+
imageVolume: IImageVolume;
|
|
43
|
+
spacingInNormalDirection: number;
|
|
44
|
+
actorUID: string;
|
|
45
|
+
} {
|
|
46
|
+
const { viewPlaneNormal } = camera;
|
|
47
|
+
const volumeActors = viewport.getActors();
|
|
48
|
+
|
|
49
|
+
if (!volumeActors || !volumeActors.length) {
|
|
50
|
+
return {
|
|
51
|
+
spacingInNormalDirection: null,
|
|
52
|
+
imageVolume: null,
|
|
53
|
+
actorUID: null,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const imageVolumes = volumeActors
|
|
58
|
+
.map((va) => {
|
|
59
|
+
// prefer the referenceUID if it is set, since it can be a derived actor
|
|
60
|
+
// and the uid does not necessarily match the volumeId
|
|
61
|
+
const actorUID = va.referenceId ?? va.uid;
|
|
62
|
+
return cache.getVolume(actorUID);
|
|
63
|
+
})
|
|
64
|
+
.filter((iv) => !!iv);
|
|
65
|
+
|
|
66
|
+
// If a volumeId is defined, set that volume as the target
|
|
67
|
+
if (targetVolumeId) {
|
|
68
|
+
const imageVolumeIndex = imageVolumes.findIndex(
|
|
69
|
+
(iv) => iv.volumeId === targetVolumeId
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const imageVolume = imageVolumes[imageVolumeIndex];
|
|
73
|
+
const { uid: actorUID } = volumeActors[imageVolumeIndex];
|
|
74
|
+
const spacingInNormalDirection = getSpacingInNormalDirection(
|
|
75
|
+
imageVolume,
|
|
76
|
+
viewPlaneNormal
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
return { imageVolume, spacingInNormalDirection, actorUID };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!imageVolumes.length) {
|
|
83
|
+
return {
|
|
84
|
+
spacingInNormalDirection: null,
|
|
85
|
+
imageVolume: null,
|
|
86
|
+
actorUID: null,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Fetch volume actor with finest resolution in direction of projection.
|
|
91
|
+
const smallest = {
|
|
92
|
+
spacingInNormalDirection: Infinity,
|
|
93
|
+
imageVolume: null,
|
|
94
|
+
actorUID: null,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const hasPrimaryVolume = imageVolumes.find(isPrimaryVolume);
|
|
98
|
+
|
|
99
|
+
for (let i = 0; i < imageVolumes.length; i++) {
|
|
100
|
+
const imageVolume = imageVolumes[i];
|
|
101
|
+
|
|
102
|
+
if (hasPrimaryVolume && !isPrimaryVolume(imageVolume)) {
|
|
103
|
+
// Secondary volumes like segmentation don't count towards spacing
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const spacingInNormalDirection = getSpacingInNormalDirection(
|
|
108
|
+
imageVolume,
|
|
109
|
+
viewPlaneNormal
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
// Allow for EPSILON part larger requirement to prefer earlier volumes
|
|
113
|
+
// when the spacing is within a factor of EPSILON. Use a factor because
|
|
114
|
+
// that deals with very small or very large volumes effectively.
|
|
115
|
+
if (
|
|
116
|
+
spacingInNormalDirection * EPSILON_PART <
|
|
117
|
+
smallest.spacingInNormalDirection
|
|
118
|
+
) {
|
|
119
|
+
smallest.spacingInNormalDirection = spacingInNormalDirection;
|
|
120
|
+
smallest.imageVolume = imageVolume;
|
|
121
|
+
smallest.actorUID = volumeActors[i].uid;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return smallest;
|
|
126
|
+
}
|