@cornerstonejs/core 5.1.0 → 5.1.2
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/esm/RenderingEngine/ContextPoolRenderingEngine.d.ts +1 -0
- package/dist/esm/RenderingEngine/ContextPoolRenderingEngine.js +16 -6
- package/dist/esm/RenderingEngine/GenericViewport/GenericViewport.d.ts +6 -1
- package/dist/esm/RenderingEngine/GenericViewport/GenericViewport.js +20 -0
- package/dist/esm/RenderingEngine/GenericViewport/Planar/CpuImageSliceRenderPath.js +12 -3
- package/dist/esm/RenderingEngine/GenericViewport/Planar/CpuVolumeSliceRenderPath.js +3 -1
- package/dist/esm/RenderingEngine/GenericViewport/Planar/DefaultPlanarDataProvider.js +5 -2
- package/dist/esm/RenderingEngine/GenericViewport/Planar/PlanarRenderPathDecisionService.d.ts +1 -0
- package/dist/esm/RenderingEngine/GenericViewport/Planar/PlanarRenderPathDecisionService.js +26 -9
- package/dist/esm/RenderingEngine/GenericViewport/Planar/PlanarViewport.d.ts +15 -2
- package/dist/esm/RenderingEngine/GenericViewport/Planar/PlanarViewport.js +174 -10
- package/dist/esm/RenderingEngine/GenericViewport/Planar/PlanarViewportTypes.d.ts +4 -2
- package/dist/esm/RenderingEngine/GenericViewport/Planar/VtkImageMapperRenderPath.js +42 -9
- package/dist/esm/RenderingEngine/GenericViewport/Planar/VtkVolumeSliceRenderPath.js +3 -1
- package/dist/esm/RenderingEngine/GenericViewport/Planar/planarImageEvents.d.ts +1 -0
- package/dist/esm/RenderingEngine/GenericViewport/Planar/planarImageEvents.js +11 -2
- package/dist/esm/RenderingEngine/GenericViewport/Planar/planarVolumePresentation.js +30 -3
- package/dist/esm/RenderingEngine/GenericViewport/Video/VideoViewport.d.ts +1 -0
- package/dist/esm/RenderingEngine/GenericViewport/Video/VideoViewport.js +3 -0
- package/dist/esm/RenderingEngine/GenericViewport/Volume3D/viewport3D.d.ts +5 -1
- package/dist/esm/RenderingEngine/GenericViewport/Volume3D/viewport3D.js +15 -1
- package/dist/esm/RenderingEngine/GenericViewport/genericViewportReferenceCompatibility.js +26 -4
- package/dist/esm/RenderingEngine/helpers/planarImageRendering.d.ts +5 -1
- package/dist/esm/RenderingEngine/helpers/planarImageRendering.js +19 -5
- package/dist/esm/RenderingEngine/helpers/stats/RenderModesPanel.d.ts +4 -0
- package/dist/esm/RenderingEngine/helpers/stats/RenderModesPanel.js +48 -1
- package/dist/esm/RenderingEngine/helpers/stats/StatsOverlay.js +66 -9
- package/dist/esm/init.js +1 -1
- package/dist/esm/types/IGenericViewport.d.ts +24 -0
- package/dist/esm/types/IGenericViewport.js +0 -0
- package/dist/esm/types/ScalingDescriptor.d.ts +5 -0
- package/dist/esm/types/ScalingDescriptor.js +0 -0
- package/dist/esm/types/index.d.ts +4 -1
- package/dist/esm/utilities/VoxelManager.d.ts +2 -0
- package/dist/esm/utilities/VoxelManager.js +12 -1
- package/dist/esm/utilities/getScalingDescriptor.d.ts +10 -0
- package/dist/esm/utilities/getScalingDescriptor.js +41 -0
- package/dist/esm/utilities/getTargetVolumeAndSpacingInNormalDir.js +4 -5
- package/dist/esm/utilities/getViewportModality.js +5 -14
- package/dist/esm/utilities/index.d.ts +4 -2
- package/dist/esm/utilities/index.js +5 -2
- package/dist/esm/utilities/isPTPrescaledWithSUV.d.ts +1 -1
- package/dist/esm/utilities/isPTPrescaledWithSUV.js +1 -1
- package/dist/esm/utilities/resolveGenericViewportVolumeId.d.ts +2 -0
- package/dist/esm/utilities/resolveGenericViewportVolumeId.js +12 -0
- package/dist/esm/utilities/viewportCapabilities.d.ts +10 -1
- package/dist/esm/utilities/viewportCapabilities.js +22 -0
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/package.json +6 -6
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Events, OrientationAxis, ViewportStatus, ViewportType, VOILUTFunctionType, } from '../../../enums';
|
|
2
2
|
import { ActorRenderMode } from '../../../types';
|
|
3
|
+
import { isImageActor } from '../../../utilities/actorCheck';
|
|
3
4
|
import cache from '../../../cache/cache';
|
|
4
5
|
import { deepClone } from '../../../utilities/deepClone';
|
|
5
6
|
import imageIdToURI from '../../../utilities/imageIdToURI';
|
|
@@ -13,6 +14,7 @@ import GenericViewport from '../GenericViewport';
|
|
|
13
14
|
import { getGenericViewportImageDisplaySet, isGenericViewportImageDisplaySet, } from '../genericViewportDisplaySetAccess';
|
|
14
15
|
import { DefaultPlanarDataProvider } from './DefaultPlanarDataProvider';
|
|
15
16
|
import { createPlanarRenderPathResolver } from './PlanarRenderPathResolver';
|
|
17
|
+
import { defaultPlanarRenderPathDecisionService } from './PlanarRenderPathDecisionService';
|
|
16
18
|
import { normalizePlanarOrientation, selectPlanarRenderPath, } from './planarRenderPathSelector';
|
|
17
19
|
import { clonePlanarOrientation } from './planarLegacyCompatibility';
|
|
18
20
|
import { normalizePlanarRotation } from './planarViewPresentation';
|
|
@@ -78,6 +80,7 @@ class PlanarViewport extends GenericViewport {
|
|
|
78
80
|
this.element.style.position = this.element.style.position || 'relative';
|
|
79
81
|
this.element.style.overflow = 'hidden';
|
|
80
82
|
this.element.style.background = this.element.style.background || '#000';
|
|
83
|
+
this.element.style.isolation = 'isolate';
|
|
81
84
|
const vtkCanvas = args.canvas;
|
|
82
85
|
const viewportElement = this.element.querySelector('.viewport-element');
|
|
83
86
|
const cpuCanvas = document.createElement('canvas');
|
|
@@ -160,7 +163,7 @@ class PlanarViewport extends GenericViewport {
|
|
|
160
163
|
const requestId = ++this.setDataRequestId;
|
|
161
164
|
const isStale = () => requestId !== this.setDataRequestId;
|
|
162
165
|
this.clearResolvedViewCache();
|
|
163
|
-
this.
|
|
166
|
+
this.removeReplaceableData(entries);
|
|
164
167
|
for (const [index, { displaySetId, options = {} }] of entries.entries()) {
|
|
165
168
|
if (isStale()) {
|
|
166
169
|
return;
|
|
@@ -201,6 +204,14 @@ class PlanarViewport extends GenericViewport {
|
|
|
201
204
|
this.setDefaultDataPresentation(dataId, {
|
|
202
205
|
visible: true,
|
|
203
206
|
});
|
|
207
|
+
const mountedPresentation = this.getDisplaySetPresentation(dataId);
|
|
208
|
+
const voiRange = mountedPresentation?.voiRange ?? this.getDefaultVOIRange(dataId);
|
|
209
|
+
if (voiRange) {
|
|
210
|
+
this.notifyDataPresentationModified(dataId, { voiRange });
|
|
211
|
+
}
|
|
212
|
+
if (role === 'source') {
|
|
213
|
+
this.triggerCameraModifiedEvent(this.getCameraForEvent());
|
|
214
|
+
}
|
|
204
215
|
}
|
|
205
216
|
removeData(dataId) {
|
|
206
217
|
this.clearResolvedViewCache();
|
|
@@ -216,6 +227,22 @@ class PlanarViewport extends GenericViewport {
|
|
|
216
227
|
getDefaultActor() {
|
|
217
228
|
return this.mountedData.getDefaultActor();
|
|
218
229
|
}
|
|
230
|
+
getImageActor(volumeId) {
|
|
231
|
+
const actorEntries = this.getActors();
|
|
232
|
+
let actorEntry = actorEntries[0];
|
|
233
|
+
if (volumeId) {
|
|
234
|
+
actorEntry =
|
|
235
|
+
actorEntries.find((a) => a.referencedId === volumeId) ??
|
|
236
|
+
this.mountedData.getActorForDataId(volumeId);
|
|
237
|
+
}
|
|
238
|
+
if (!actorEntry || !isImageActor(actorEntry)) {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
return actorEntry.actor;
|
|
242
|
+
}
|
|
243
|
+
getDefaultImageData() {
|
|
244
|
+
return this.getImageActor()?.getMapper().getInputData();
|
|
245
|
+
}
|
|
219
246
|
renderImageObject(image) {
|
|
220
247
|
const dataId = `image-object:${this.id}:${imageIdToURI(image.imageId)}`;
|
|
221
248
|
if (this.renderImageObjectDataId &&
|
|
@@ -297,6 +324,58 @@ class PlanarViewport extends GenericViewport {
|
|
|
297
324
|
getSourceDataId() {
|
|
298
325
|
return this.mountedData.getActiveDataId();
|
|
299
326
|
}
|
|
327
|
+
getCurrentMode() {
|
|
328
|
+
const binding = this.getSourceBinding();
|
|
329
|
+
if (!binding) {
|
|
330
|
+
return 'empty';
|
|
331
|
+
}
|
|
332
|
+
const renderMode = binding.rendering.renderMode;
|
|
333
|
+
if (renderMode === ActorRenderMode.VTK_VOLUME ||
|
|
334
|
+
renderMode === ActorRenderMode.VTK_VOLUME_SLICE ||
|
|
335
|
+
renderMode === ActorRenderMode.CPU_VOLUME) {
|
|
336
|
+
return 'volume';
|
|
337
|
+
}
|
|
338
|
+
if (renderMode === ActorRenderMode.VTK_IMAGE ||
|
|
339
|
+
renderMode === ActorRenderMode.CPU_IMAGE) {
|
|
340
|
+
return 'stack';
|
|
341
|
+
}
|
|
342
|
+
return this.getVolumeId() ? 'volume' : 'stack';
|
|
343
|
+
}
|
|
344
|
+
notifyDataPresentationModified(displaySetId, props) {
|
|
345
|
+
if (this.suppressEvents) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
const voiTouched = 'voiRange' in props || 'invert' in props || 'voiLUTFunction' in props;
|
|
349
|
+
const colormapTouched = 'colormap' in props;
|
|
350
|
+
if (!voiTouched && !colormapTouched) {
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
const presentation = this.getDisplaySetPresentation(displaySetId);
|
|
354
|
+
const volumeId = this.getDataSet(displaySetId)?.volumeId ??
|
|
355
|
+
(displaySetId === this.getSourceDataId()
|
|
356
|
+
? this.getVolumeId()
|
|
357
|
+
: undefined);
|
|
358
|
+
if (voiTouched) {
|
|
359
|
+
const range = props.voiRange ?? presentation?.voiRange;
|
|
360
|
+
if (range) {
|
|
361
|
+
triggerEvent(this.element, Events.VOI_MODIFIED, {
|
|
362
|
+
viewportId: this.id,
|
|
363
|
+
range,
|
|
364
|
+
volumeId,
|
|
365
|
+
VOILUTFunction: props.voiLUTFunction ?? presentation?.voiLUTFunction,
|
|
366
|
+
invert: props.invert ?? presentation?.invert,
|
|
367
|
+
colormap: presentation?.colormap,
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
if (colormapTouched && props.colormap) {
|
|
372
|
+
triggerEvent(this.element, Events.COLORMAP_MODIFIED, {
|
|
373
|
+
viewportId: this.id,
|
|
374
|
+
colormap: props.colormap,
|
|
375
|
+
volumeId,
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
}
|
|
300
379
|
getDefaultVOIRange(dataId) {
|
|
301
380
|
return this.mountedData.getDefaultVOIRange(dataId);
|
|
302
381
|
}
|
|
@@ -309,6 +388,25 @@ class PlanarViewport extends GenericViewport {
|
|
|
309
388
|
getSliceIndex() {
|
|
310
389
|
return this.getCurrentImageIdIndex();
|
|
311
390
|
}
|
|
391
|
+
getNumberOfSlices() {
|
|
392
|
+
const imageCount = this.getImageIds().length;
|
|
393
|
+
if (!imageCount) {
|
|
394
|
+
return 0;
|
|
395
|
+
}
|
|
396
|
+
return this.getMaxImageIdIndex() + 1;
|
|
397
|
+
}
|
|
398
|
+
calibrateSpacing(imageId) {
|
|
399
|
+
this.render();
|
|
400
|
+
const cpuImageData = this.getImageData();
|
|
401
|
+
triggerEvent(this.element, Events.IMAGE_SPACING_CALIBRATED, {
|
|
402
|
+
element: this.element,
|
|
403
|
+
viewportId: this.id,
|
|
404
|
+
renderingEngineId: this.renderingEngineId,
|
|
405
|
+
imageId,
|
|
406
|
+
calibration: cpuImageData?.calibration,
|
|
407
|
+
imageData: cpuImageData?.imageData,
|
|
408
|
+
});
|
|
409
|
+
}
|
|
312
410
|
normalizeViewState(viewState) {
|
|
313
411
|
return normalizePlanarViewState(viewState);
|
|
314
412
|
}
|
|
@@ -546,6 +644,19 @@ class PlanarViewport extends GenericViewport {
|
|
|
546
644
|
setOrientation(orientation) {
|
|
547
645
|
this.setViewState({ orientation: this.resolveSetOrientation(orientation) });
|
|
548
646
|
}
|
|
647
|
+
canRenderOrientation(orientation, dataId) {
|
|
648
|
+
const targetDataId = dataId ?? this.getSourceDataId();
|
|
649
|
+
if (!targetDataId) {
|
|
650
|
+
return false;
|
|
651
|
+
}
|
|
652
|
+
const dataSet = this.getDataSet(targetDataId);
|
|
653
|
+
if (!dataSet) {
|
|
654
|
+
return false;
|
|
655
|
+
}
|
|
656
|
+
return defaultPlanarRenderPathDecisionService.canRender(dataSet, {
|
|
657
|
+
orientation: this.resolveSetOrientation(orientation),
|
|
658
|
+
});
|
|
659
|
+
}
|
|
549
660
|
resetViewState(options) {
|
|
550
661
|
const { resetPan = true, resetZoom = true, resetOrientation = true, resetFlip = true, } = options || {};
|
|
551
662
|
const nextCamera = {
|
|
@@ -573,6 +684,22 @@ class PlanarViewport extends GenericViewport {
|
|
|
573
684
|
resetViewStateForResize() {
|
|
574
685
|
return this.resetViewState();
|
|
575
686
|
}
|
|
687
|
+
resetDisplaySetPresentation(dataId) {
|
|
688
|
+
if (this.isDestroyed) {
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
const targetDataId = dataId ?? this.getSourceDataId();
|
|
692
|
+
if (!targetDataId) {
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
this.setDataPresentationState(targetDataId, {});
|
|
696
|
+
const defaultVOIRange = this.getDefaultVOIRange(targetDataId);
|
|
697
|
+
if (defaultVOIRange) {
|
|
698
|
+
this.notifyDataPresentationModified(targetDataId, {
|
|
699
|
+
voiRange: defaultVOIRange,
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
}
|
|
576
703
|
getUseCustomRenderingPipeline() {
|
|
577
704
|
return false;
|
|
578
705
|
}
|
|
@@ -584,6 +711,7 @@ class PlanarViewport extends GenericViewport {
|
|
|
584
711
|
return;
|
|
585
712
|
}
|
|
586
713
|
this.clearResolvedViewCache();
|
|
714
|
+
this.cachedCanvasDimensions = undefined;
|
|
587
715
|
const { clientHeight, clientWidth } = this.element;
|
|
588
716
|
const { canvas } = this.renderContext.cpu;
|
|
589
717
|
const devicePixelRatio = window.devicePixelRatio || 1;
|
|
@@ -728,6 +856,12 @@ class PlanarViewport extends GenericViewport {
|
|
|
728
856
|
}
|
|
729
857
|
getActiveImageIdIndex() {
|
|
730
858
|
const binding = this.getCurrentBinding();
|
|
859
|
+
if (this.getCurrentMode() === 'volume') {
|
|
860
|
+
const resolvedImageIdIndex = this.getResolvedView()?.state?.currentImageIdIndex;
|
|
861
|
+
if (typeof resolvedImageIdIndex === 'number') {
|
|
862
|
+
return resolvedImageIdIndex;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
731
865
|
const currentImageIdIndex = binding?.rendering?.currentImageIdIndex;
|
|
732
866
|
if (typeof currentImageIdIndex === 'number') {
|
|
733
867
|
return currentImageIdIndex;
|
|
@@ -757,6 +891,27 @@ class PlanarViewport extends GenericViewport {
|
|
|
757
891
|
getCurrentBinding() {
|
|
758
892
|
return this.mountedData.getCurrentBinding();
|
|
759
893
|
}
|
|
894
|
+
removeReplaceableData(entries) {
|
|
895
|
+
const nextSourceId = this.resolveNextSourceId(entries);
|
|
896
|
+
const preserveSegmentationOverlays = nextSourceId !== undefined && nextSourceId === this.getSourceDataId();
|
|
897
|
+
for (const dataId of Array.from(this.bindings.keys())) {
|
|
898
|
+
if (preserveSegmentationOverlays &&
|
|
899
|
+
this.isSegmentationOverlayBinding(dataId)) {
|
|
900
|
+
continue;
|
|
901
|
+
}
|
|
902
|
+
this.removeData(dataId);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
resolveNextSourceId(entries) {
|
|
906
|
+
const explicitSource = entries.find((entry) => entry.options?.role === 'source');
|
|
907
|
+
return explicitSource?.displaySetId ?? entries[0]?.displaySetId;
|
|
908
|
+
}
|
|
909
|
+
isSegmentationOverlayBinding(dataId) {
|
|
910
|
+
if (this.getDisplaySetRole(dataId) !== 'overlay') {
|
|
911
|
+
return false;
|
|
912
|
+
}
|
|
913
|
+
return this.getDataSet(dataId)?.reference?.kind === 'segmentation';
|
|
914
|
+
}
|
|
760
915
|
getDataSet(dataId) {
|
|
761
916
|
const dataSet = getGenericViewportImageDisplaySet(dataId);
|
|
762
917
|
if (!isPlanarRegisteredDataSet(dataSet)) {
|
|
@@ -773,6 +928,7 @@ class PlanarViewport extends GenericViewport {
|
|
|
773
928
|
const selectedPath = selectPlanarRenderPath(dataSet, {
|
|
774
929
|
orientation: resolvedOrientation,
|
|
775
930
|
cpuThresholds: options.cpuThresholds,
|
|
931
|
+
webGLAvailable: options.forceCPU ? false : undefined,
|
|
776
932
|
});
|
|
777
933
|
const data = await this.dataProvider.load(dataId, {
|
|
778
934
|
acquisitionOrientation: selectedPath.acquisitionOrientation,
|
|
@@ -794,7 +950,7 @@ class PlanarViewport extends GenericViewport {
|
|
|
794
950
|
? this.createInitialVolumeSliceState(planarData, selectedPath.renderMode, orientation)
|
|
795
951
|
: {
|
|
796
952
|
kind: 'stackIndex',
|
|
797
|
-
imageIdIndex: planarData.initialImageIdIndex,
|
|
953
|
+
imageIdIndex: planarData.initialImageIdIndex ?? 0,
|
|
798
954
|
};
|
|
799
955
|
this.clearResolvedViewCache();
|
|
800
956
|
this.viewState = this.normalizeViewState({
|
|
@@ -934,6 +1090,20 @@ class PlanarViewport extends GenericViewport {
|
|
|
934
1090
|
}
|
|
935
1091
|
return snapshot;
|
|
936
1092
|
}
|
|
1093
|
+
getCachedPlanarCanvasDimensions(rendering) {
|
|
1094
|
+
const cached = this.cachedCanvasDimensions;
|
|
1095
|
+
if (cached) {
|
|
1096
|
+
return cached;
|
|
1097
|
+
}
|
|
1098
|
+
const dimensions = getPlanarViewStateCanvasDimensions({
|
|
1099
|
+
renderContext: this.renderContext,
|
|
1100
|
+
rendering,
|
|
1101
|
+
});
|
|
1102
|
+
if (dimensions.canvasHeight > 0 && dimensions.canvasWidth > 0) {
|
|
1103
|
+
this.cachedCanvasDimensions = dimensions;
|
|
1104
|
+
}
|
|
1105
|
+
return dimensions;
|
|
1106
|
+
}
|
|
937
1107
|
resolveCachedViewState(viewState, args = {}) {
|
|
938
1108
|
if (args.frameOfReferenceUID || typeof args.sliceIndex === 'number') {
|
|
939
1109
|
return this.resolveViewState(viewState, args);
|
|
@@ -946,10 +1116,7 @@ class PlanarViewport extends GenericViewport {
|
|
|
946
1116
|
if (!rendering) {
|
|
947
1117
|
return this.resolveViewState(viewState, args);
|
|
948
1118
|
}
|
|
949
|
-
const { canvasHeight, canvasWidth } =
|
|
950
|
-
renderContext: this.renderContext,
|
|
951
|
-
rendering,
|
|
952
|
-
});
|
|
1119
|
+
const { canvasHeight, canvasWidth } = this.getCachedPlanarCanvasDimensions(rendering);
|
|
953
1120
|
if (cache &&
|
|
954
1121
|
cache.activeDataId === activeDataId &&
|
|
955
1122
|
cache.viewState === viewState &&
|
|
@@ -1073,10 +1240,7 @@ class PlanarViewport extends GenericViewport {
|
|
|
1073
1240
|
getCurrentCanvasDimensions() {
|
|
1074
1241
|
const rendering = this.getCurrentPlanarRendering();
|
|
1075
1242
|
if (rendering) {
|
|
1076
|
-
const { canvasHeight, canvasWidth } =
|
|
1077
|
-
renderContext: this.renderContext,
|
|
1078
|
-
rendering,
|
|
1079
|
-
});
|
|
1243
|
+
const { canvasHeight, canvasWidth } = this.getCachedPlanarCanvasDimensions(rendering);
|
|
1080
1244
|
return {
|
|
1081
1245
|
width: canvasWidth || this.element.clientWidth,
|
|
1082
1246
|
height: canvasHeight || this.element.clientHeight,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type vtkRenderer from '@kitware/vtk.js/Rendering/Core/Renderer';
|
|
2
2
|
import type vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData';
|
|
3
|
-
import type { BlendModes, InterpolationType, OrientationAxis } from '../../../enums';
|
|
3
|
+
import type { BlendModes, InterpolationType, OrientationAxis, VOILUTFunctionType } from '../../../enums';
|
|
4
4
|
import type { ActorEntry, ActorRenderMode, ColormapPublic, IImage, IImageVolume, OrientationVectors, Point2, Point3, VOIRange } from '../../../types';
|
|
5
5
|
import type { ViewportInput } from '../../../types/IViewport';
|
|
6
6
|
import type ViewportInputOptions from '../../../types/ViewportInputOptions';
|
|
@@ -28,6 +28,7 @@ export interface PlanarSetDataOptions {
|
|
|
28
28
|
image?: number;
|
|
29
29
|
volume?: number;
|
|
30
30
|
};
|
|
31
|
+
forceCPU?: boolean;
|
|
31
32
|
role?: BindingRole;
|
|
32
33
|
}
|
|
33
34
|
export interface PlanarDataLoadOptions {
|
|
@@ -38,7 +39,7 @@ export interface PlanarDataLoadOptions {
|
|
|
38
39
|
}
|
|
39
40
|
export interface PlanarPayload {
|
|
40
41
|
imageIds: string[];
|
|
41
|
-
initialImageIdIndex
|
|
42
|
+
initialImageIdIndex?: number;
|
|
42
43
|
volumeId: string;
|
|
43
44
|
renderMode: PlanarEffectiveRenderMode;
|
|
44
45
|
acquisitionOrientation?: PlanarViewState['orientation'];
|
|
@@ -51,6 +52,7 @@ export interface PlanarPayload {
|
|
|
51
52
|
export interface PlanarPresentationProps extends BasePresentationProps {
|
|
52
53
|
colormap?: ColormapPublic;
|
|
53
54
|
voiRange?: VOIRange;
|
|
55
|
+
voiLUTFunction?: VOILUTFunctionType;
|
|
54
56
|
invert?: boolean;
|
|
55
57
|
}
|
|
56
58
|
export type PlanarDisplayArea = Omit<DisplayArea, 'scale'> & {
|
|
@@ -4,9 +4,10 @@ import { buildPlanarActorEntry } from './buildPlanarActorEntry';
|
|
|
4
4
|
import uuidv4 from '../../../utilities/uuidv4';
|
|
5
5
|
import { InterpolationType, MetadataModules, ViewportType, } from '../../../enums';
|
|
6
6
|
import { loadAndCacheImage } from '../../../loaders/imageLoader';
|
|
7
|
+
import { updateVTKImageDataWithCornerstoneImage } from '../../../utilities/updateVTKImageDataWithCornerstoneImage';
|
|
7
8
|
import * as metaData from '../../../metaData';
|
|
8
9
|
import { ActorRenderMode } from '../../../types';
|
|
9
|
-
import { applyPlanarImagePresentation, createVTKImageDataFromImage, getDefaultImageVOIRange, } from '../../helpers/planarImageRendering';
|
|
10
|
+
import { applyPlanarImagePresentation, createVTKImageDataFromImage, getDefaultImageVOIRange, updateVTKImageDataGeometryFromImage, } from '../../helpers/planarImageRendering';
|
|
10
11
|
import { canvasToWorldContextPool, worldToCanvasContextPool, } from './planarAdapterCoordinateTransforms';
|
|
11
12
|
import { buildPlanarImageData } from './CpuImageSliceRenderPath';
|
|
12
13
|
import { triggerPlanarNewImage } from './planarImageEvents';
|
|
@@ -24,6 +25,9 @@ export class VtkImageMapperRenderPath {
|
|
|
24
25
|
ctx.display.activateRenderMode(ActorRenderMode.VTK_IMAGE);
|
|
25
26
|
mapper.setInputData(imageData);
|
|
26
27
|
actor.setMapper(mapper);
|
|
28
|
+
if (imageData.getPointData().getScalars().getNumberOfComponents() > 1) {
|
|
29
|
+
actor.getProperty().setIndependentComponents(false);
|
|
30
|
+
}
|
|
27
31
|
ctx.vtk.renderer.addActor(actor);
|
|
28
32
|
const rendering = {
|
|
29
33
|
renderMode: ActorRenderMode.VTK_IMAGE,
|
|
@@ -33,15 +37,17 @@ export class VtkImageMapperRenderPath {
|
|
|
33
37
|
mapper,
|
|
34
38
|
imageData,
|
|
35
39
|
useWorldCoordinateImageData: payload.useWorldCoordinateImageData,
|
|
36
|
-
currentImageIdIndex: payload.initialImageIdIndex,
|
|
40
|
+
currentImageIdIndex: payload.initialImageIdIndex ?? 0,
|
|
37
41
|
defaultVOIRange: getDefaultImageVOIRange(payload.image),
|
|
38
42
|
dataPresentation: undefined,
|
|
39
43
|
loadRequestId: 0,
|
|
40
44
|
};
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
if (options.role !== 'overlay') {
|
|
46
|
+
triggerPlanarNewImage(ctx, {
|
|
47
|
+
image: payload.image,
|
|
48
|
+
imageIdIndex: payload.initialImageIdIndex ?? 0,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
45
51
|
return {
|
|
46
52
|
rendering,
|
|
47
53
|
updateDataPresentation: (props) => {
|
|
@@ -79,6 +85,7 @@ export class VtkImageMapperRenderPath {
|
|
|
79
85
|
applyPlanarImagePresentation({
|
|
80
86
|
actor: rendering.actor,
|
|
81
87
|
defaultVOIRange: rendering.defaultVOIRange,
|
|
88
|
+
defaultVOILUTFunction: rendering.currentImage?.voiLUTFunction,
|
|
82
89
|
props: {
|
|
83
90
|
interpolationType: InterpolationType.LINEAR,
|
|
84
91
|
...dataPresentation,
|
|
@@ -215,8 +222,15 @@ async function updateRenderedImage(args) {
|
|
|
215
222
|
const { ctx, dataId, image, rendering, imageIdIndex, dataPresentation } = args;
|
|
216
223
|
const camera = ctx.viewport.getViewState();
|
|
217
224
|
const { actor, mapper } = rendering;
|
|
218
|
-
|
|
219
|
-
|
|
225
|
+
let imageData = rendering.imageData;
|
|
226
|
+
if (imageData && canReuseImageDataForImage(imageData, image)) {
|
|
227
|
+
updateVTKImageDataWithCornerstoneImage(imageData, image);
|
|
228
|
+
updateVTKImageDataGeometryFromImage(imageData, image);
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
imageData = createVTKImageDataFromImage(image);
|
|
232
|
+
mapper.setInputData(imageData);
|
|
233
|
+
}
|
|
220
234
|
rendering.currentImage = image;
|
|
221
235
|
rendering.imageData = imageData;
|
|
222
236
|
rendering.currentImageIdIndex = imageIdIndex;
|
|
@@ -228,6 +242,7 @@ async function updateRenderedImage(args) {
|
|
|
228
242
|
applyPlanarImagePresentation({
|
|
229
243
|
actor,
|
|
230
244
|
defaultVOIRange: rendering.defaultVOIRange,
|
|
245
|
+
defaultVOILUTFunction: image.voiLUTFunction,
|
|
231
246
|
props: {
|
|
232
247
|
interpolationType: dataPresentation?.interpolationType ?? InterpolationType.LINEAR,
|
|
233
248
|
...dataPresentation,
|
|
@@ -248,9 +263,27 @@ async function updateRenderedImage(args) {
|
|
|
248
263
|
});
|
|
249
264
|
}
|
|
250
265
|
}
|
|
251
|
-
|
|
266
|
+
if (projection?.isSourceBinding) {
|
|
267
|
+
triggerPlanarNewImage(ctx, { image, imageIdIndex });
|
|
268
|
+
}
|
|
252
269
|
ctx.display.requestRender();
|
|
253
270
|
}
|
|
271
|
+
function canReuseImageDataForImage(imageData, image) {
|
|
272
|
+
const scalars = imageData.getPointData?.()?.getScalars?.();
|
|
273
|
+
if (!scalars) {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
const [xVoxels, yVoxels] = imageData.getDimensions();
|
|
277
|
+
if (xVoxels !== image.columns || yVoxels !== image.rows) {
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
if (scalars.getDataType() !==
|
|
281
|
+
image.voxelManager.getScalarData().constructor.name) {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
const incomingComponents = image.color ? 3 : 1;
|
|
285
|
+
return scalars.getNumberOfComponents() === incomingComponents;
|
|
286
|
+
}
|
|
254
287
|
function applyPlanarImageActorTransforms(ctx, rendering, activeSourceICamera) {
|
|
255
288
|
applyPlanarICameraToActor({
|
|
256
289
|
actor: rendering.actor,
|
|
@@ -36,7 +36,7 @@ export class VtkVolumeSliceRenderPath {
|
|
|
36
36
|
imageIds: payload.imageIds,
|
|
37
37
|
acquisitionOrientation: payload.acquisitionOrientation,
|
|
38
38
|
mapper,
|
|
39
|
-
currentImageIdIndex: payload.initialImageIdIndex,
|
|
39
|
+
currentImageIdIndex: payload.initialImageIdIndex ?? 0,
|
|
40
40
|
maxImageIdIndex: payload.imageIds.length - 1,
|
|
41
41
|
defaultVOIRange: defaultRange
|
|
42
42
|
? { lower: defaultRange[0], upper: defaultRange[1] }
|
|
@@ -60,6 +60,7 @@ export class VtkVolumeSliceRenderPath {
|
|
|
60
60
|
acquisitionOrientation: rendering.acquisitionOrientation,
|
|
61
61
|
imageIds: rendering.imageIds,
|
|
62
62
|
imageIdIndex: rendering.currentImageIdIndex,
|
|
63
|
+
maxImageIdIndex: rendering.maxImageIdIndex,
|
|
63
64
|
});
|
|
64
65
|
return {
|
|
65
66
|
rendering,
|
|
@@ -154,6 +155,7 @@ export class VtkVolumeSliceRenderPath {
|
|
|
154
155
|
acquisitionOrientation: rendering.acquisitionOrientation,
|
|
155
156
|
imageIds: rendering.imageIds,
|
|
156
157
|
imageIdIndex: rendering.currentImageIdIndex,
|
|
158
|
+
maxImageIdIndex: rendering.maxImageIdIndex,
|
|
157
159
|
});
|
|
158
160
|
}
|
|
159
161
|
}
|
|
@@ -22,7 +22,16 @@ export function triggerPlanarVolumeNewImage(ctx, params) {
|
|
|
22
22
|
imageId: params.imageIds[params.imageIdIndex],
|
|
23
23
|
imageIdIndex: params.imageIdIndex,
|
|
24
24
|
});
|
|
25
|
-
return;
|
|
26
25
|
}
|
|
27
|
-
|
|
26
|
+
else {
|
|
27
|
+
triggerPlanarNewImage(ctx);
|
|
28
|
+
}
|
|
29
|
+
if (typeof params.imageIdIndex === 'number') {
|
|
30
|
+
triggerEvent(ctx.viewport.element, Events.VOLUME_NEW_IMAGE, {
|
|
31
|
+
imageIndex: params.imageIdIndex,
|
|
32
|
+
numberOfSlices: params.maxImageIdIndex + 1,
|
|
33
|
+
viewportId: ctx.viewportId,
|
|
34
|
+
renderingEngineId: ctx.renderingEngineId,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
28
37
|
}
|
|
@@ -61,7 +61,10 @@ function applyColormapOpacity(args) {
|
|
|
61
61
|
if (colormap.opacity === undefined && colormap.threshold === undefined) {
|
|
62
62
|
return;
|
|
63
63
|
}
|
|
64
|
-
const range =
|
|
64
|
+
const range = colormap.threshold !== undefined
|
|
65
|
+
? (getActorScalarRange(actor) ??
|
|
66
|
+
resolveVolumeOpacityRange(actor, voiRange))
|
|
67
|
+
: resolveVolumeOpacityRange(actor, voiRange);
|
|
65
68
|
const opacityFunction = vtkPiecewiseFunction.newInstance();
|
|
66
69
|
const points = buildColormapOpacityPoints({
|
|
67
70
|
colormap,
|
|
@@ -75,7 +78,9 @@ function applyColormapOpacity(args) {
|
|
|
75
78
|
}
|
|
76
79
|
function buildColormapOpacityPoints(args) {
|
|
77
80
|
const { colormap, range, fallbackOpacity } = args;
|
|
78
|
-
const
|
|
81
|
+
const span = range.upper - range.lower;
|
|
82
|
+
const delta = Math.max(Math.abs(span) * 0.001, 1e-3);
|
|
83
|
+
const toScalar = (normalized) => range.lower + normalized * span;
|
|
79
84
|
const threshold = colormap.threshold !== undefined
|
|
80
85
|
? Math.max(range.lower, Math.min(range.upper, colormap.threshold))
|
|
81
86
|
: undefined;
|
|
@@ -83,7 +88,7 @@ function buildColormapOpacityPoints(args) {
|
|
|
83
88
|
const sortedPoints = colormap.opacity
|
|
84
89
|
.map(({ opacity, value }) => ({
|
|
85
90
|
opacity: clampToUnit(opacity),
|
|
86
|
-
value,
|
|
91
|
+
value: toScalar(value),
|
|
87
92
|
}))
|
|
88
93
|
.sort((a, b) => a.value - b.value);
|
|
89
94
|
if (threshold === undefined) {
|
|
@@ -185,6 +190,28 @@ function getVolumeNumberOfComponents(actor) {
|
|
|
185
190
|
imageDataMetadata?.numberOfComponents ??
|
|
186
191
|
1);
|
|
187
192
|
}
|
|
193
|
+
function getActorScalarRange(actor) {
|
|
194
|
+
const mapperInputData = actor.getMapper()?.getInputData?.();
|
|
195
|
+
const voxelManager = mapperInputData?.get?.('voxelManager')?.voxelManager;
|
|
196
|
+
const voxelRange = voxelManager?.getRange?.();
|
|
197
|
+
if (voxelRange &&
|
|
198
|
+
Number.isFinite(voxelRange[0]) &&
|
|
199
|
+
Number.isFinite(voxelRange[1]) &&
|
|
200
|
+
voxelRange[1] > voxelRange[0]) {
|
|
201
|
+
return { lower: voxelRange[0], upper: voxelRange[1] };
|
|
202
|
+
}
|
|
203
|
+
const scalarRange = mapperInputData
|
|
204
|
+
?.getPointData?.()
|
|
205
|
+
?.getScalars?.()
|
|
206
|
+
?.getRange?.();
|
|
207
|
+
if (scalarRange &&
|
|
208
|
+
Number.isFinite(scalarRange[0]) &&
|
|
209
|
+
Number.isFinite(scalarRange[1]) &&
|
|
210
|
+
scalarRange[1] > scalarRange[0]) {
|
|
211
|
+
return { lower: scalarRange[0], upper: scalarRange[1] };
|
|
212
|
+
}
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
188
215
|
function resolveVolumeOpacityRange(actor, voiRange) {
|
|
189
216
|
if (voiRange &&
|
|
190
217
|
Number.isFinite(voiRange.lower) &&
|
|
@@ -41,6 +41,7 @@ declare class VideoViewport extends GenericViewport<VideoViewState, VideoDataPre
|
|
|
41
41
|
getCurrentImageIdIndex(): number;
|
|
42
42
|
getImageIds(): string[];
|
|
43
43
|
render(): void;
|
|
44
|
+
customRenderViewportToCanvas(): void;
|
|
44
45
|
resetViewState(): boolean;
|
|
45
46
|
dispose(): void;
|
|
46
47
|
getUseCustomRenderingPipeline(): boolean;
|
|
@@ -41,7 +41,11 @@ declare class VolumeViewport3D extends GenericViewport<Volume3DCamera, Volume3DD
|
|
|
41
41
|
getImageData(): IImageData | undefined;
|
|
42
42
|
getActors(): ActorEntry[];
|
|
43
43
|
getDefaultActor(): ActorEntry | undefined;
|
|
44
|
-
resetViewState(
|
|
44
|
+
resetViewState(options?: {
|
|
45
|
+
resetPan?: boolean;
|
|
46
|
+
resetZoom?: boolean;
|
|
47
|
+
resetToCenter?: boolean;
|
|
48
|
+
}): boolean;
|
|
45
49
|
resetViewStateForResize(): boolean;
|
|
46
50
|
resize(): void;
|
|
47
51
|
render(): void;
|
|
@@ -25,6 +25,7 @@ class VolumeViewport3D extends GenericViewport {
|
|
|
25
25
|
this.suppressEvents = false;
|
|
26
26
|
this.renderingEngineId = args.renderingEngineId;
|
|
27
27
|
this.canvas = args.canvas;
|
|
28
|
+
this.canvas.style.display = '';
|
|
28
29
|
this.sWidth = args.sWidth;
|
|
29
30
|
this.sHeight = args.sHeight;
|
|
30
31
|
this.defaultOptions = args.defaultOptions || {};
|
|
@@ -230,11 +231,24 @@ class VolumeViewport3D extends GenericViewport {
|
|
|
230
231
|
}
|
|
231
232
|
return this.getActorEntriesForRendering(this.getVolume3DRendering(binding), data)[0];
|
|
232
233
|
}
|
|
233
|
-
resetViewState() {
|
|
234
|
+
resetViewState(options) {
|
|
235
|
+
const { resetPan = true, resetZoom = true, resetToCenter = true, } = options || {};
|
|
234
236
|
const previousCamera = this.getCameraForEvent();
|
|
235
237
|
const renderer = this.getRenderer();
|
|
238
|
+
const camera = renderer.getActiveCamera();
|
|
239
|
+
const previousParallelScale = camera.getParallelScale();
|
|
240
|
+
const previousPosition = camera.getPosition();
|
|
241
|
+
const previousFocalPoint = camera.getFocalPoint();
|
|
236
242
|
renderer.resetCamera();
|
|
237
243
|
renderer.resetCameraClippingRange();
|
|
244
|
+
if (!resetZoom) {
|
|
245
|
+
camera.setParallelScale(previousParallelScale);
|
|
246
|
+
}
|
|
247
|
+
if (!resetPan || !resetToCenter) {
|
|
248
|
+
camera.setFocalPoint(...previousFocalPoint);
|
|
249
|
+
camera.setPosition(...previousPosition);
|
|
250
|
+
renderer.resetCameraClippingRange();
|
|
251
|
+
}
|
|
238
252
|
this.viewState = this.getViewState();
|
|
239
253
|
this.render();
|
|
240
254
|
this.triggerCameraModifiedEvent(previousCamera);
|
|
@@ -189,15 +189,32 @@ function getReferencedImageRangeEndIndex(imageURIEntries, viewRef) {
|
|
|
189
189
|
const rangeEndSliceIndex = imageURIEntries.findIndex((imageURIEntry) => imageURIEntryMatches(imageURIEntry, rangeEndImageURI));
|
|
190
190
|
return rangeEndSliceIndex === -1 ? undefined : rangeEndSliceIndex;
|
|
191
191
|
}
|
|
192
|
+
const imageIdCompatibleURIsCache = new Map();
|
|
193
|
+
const imageIdsURIEntriesCache = new WeakMap();
|
|
194
|
+
function getImageIdsURIEntries(imageIds) {
|
|
195
|
+
let entries = imageIdsURIEntriesCache.get(imageIds);
|
|
196
|
+
if (!entries) {
|
|
197
|
+
entries = imageIds.map((imageId) => getImageIdCompatibleURIs(imageId));
|
|
198
|
+
imageIdsURIEntriesCache.set(imageIds, entries);
|
|
199
|
+
}
|
|
200
|
+
return entries;
|
|
201
|
+
}
|
|
192
202
|
function getContextImageURIEntries(context) {
|
|
193
|
-
const
|
|
203
|
+
const baseEntries = context.imageIds?.length
|
|
204
|
+
? getImageIdsURIEntries(context.imageIds)
|
|
205
|
+
: [];
|
|
206
|
+
const extraImageURIs = context.imageURIs;
|
|
207
|
+
if (!extraImageURIs?.length) {
|
|
208
|
+
return baseEntries;
|
|
209
|
+
}
|
|
210
|
+
const imageURIEntries = baseEntries.slice();
|
|
194
211
|
const knownImageURIs = new Set();
|
|
195
|
-
for (const imageURIEntry of
|
|
212
|
+
for (const imageURIEntry of baseEntries) {
|
|
196
213
|
for (const imageURI of imageURIEntry) {
|
|
197
214
|
knownImageURIs.add(imageURI);
|
|
198
215
|
}
|
|
199
216
|
}
|
|
200
|
-
for (const imageURI of
|
|
217
|
+
for (const imageURI of extraImageURIs) {
|
|
201
218
|
if (!knownImageURIs.has(imageURI)) {
|
|
202
219
|
imageURIEntries.push([imageURI]);
|
|
203
220
|
knownImageURIs.add(imageURI);
|
|
@@ -206,7 +223,12 @@ function getContextImageURIEntries(context) {
|
|
|
206
223
|
return imageURIEntries;
|
|
207
224
|
}
|
|
208
225
|
function getImageIdCompatibleURIs(imageId) {
|
|
209
|
-
|
|
226
|
+
let uris = imageIdCompatibleURIsCache.get(imageId);
|
|
227
|
+
if (!uris) {
|
|
228
|
+
uris = uniqueStrings([imageIdToURI(imageId), getLegacyImageIdURI(imageId)]);
|
|
229
|
+
imageIdCompatibleURIsCache.set(imageId, uris);
|
|
230
|
+
}
|
|
231
|
+
return uris;
|
|
210
232
|
}
|
|
211
233
|
function getLegacyImageIdURI(imageId) {
|
|
212
234
|
const colonIndex = imageId.indexOf(':');
|