@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.
Files changed (50) hide show
  1. package/dist/esm/RenderingEngine/ContextPoolRenderingEngine.d.ts +1 -0
  2. package/dist/esm/RenderingEngine/ContextPoolRenderingEngine.js +16 -6
  3. package/dist/esm/RenderingEngine/GenericViewport/GenericViewport.d.ts +6 -1
  4. package/dist/esm/RenderingEngine/GenericViewport/GenericViewport.js +20 -0
  5. package/dist/esm/RenderingEngine/GenericViewport/Planar/CpuImageSliceRenderPath.js +12 -3
  6. package/dist/esm/RenderingEngine/GenericViewport/Planar/CpuVolumeSliceRenderPath.js +3 -1
  7. package/dist/esm/RenderingEngine/GenericViewport/Planar/DefaultPlanarDataProvider.js +5 -2
  8. package/dist/esm/RenderingEngine/GenericViewport/Planar/PlanarRenderPathDecisionService.d.ts +1 -0
  9. package/dist/esm/RenderingEngine/GenericViewport/Planar/PlanarRenderPathDecisionService.js +26 -9
  10. package/dist/esm/RenderingEngine/GenericViewport/Planar/PlanarViewport.d.ts +15 -2
  11. package/dist/esm/RenderingEngine/GenericViewport/Planar/PlanarViewport.js +174 -10
  12. package/dist/esm/RenderingEngine/GenericViewport/Planar/PlanarViewportTypes.d.ts +4 -2
  13. package/dist/esm/RenderingEngine/GenericViewport/Planar/VtkImageMapperRenderPath.js +42 -9
  14. package/dist/esm/RenderingEngine/GenericViewport/Planar/VtkVolumeSliceRenderPath.js +3 -1
  15. package/dist/esm/RenderingEngine/GenericViewport/Planar/planarImageEvents.d.ts +1 -0
  16. package/dist/esm/RenderingEngine/GenericViewport/Planar/planarImageEvents.js +11 -2
  17. package/dist/esm/RenderingEngine/GenericViewport/Planar/planarVolumePresentation.js +30 -3
  18. package/dist/esm/RenderingEngine/GenericViewport/Video/VideoViewport.d.ts +1 -0
  19. package/dist/esm/RenderingEngine/GenericViewport/Video/VideoViewport.js +3 -0
  20. package/dist/esm/RenderingEngine/GenericViewport/Volume3D/viewport3D.d.ts +5 -1
  21. package/dist/esm/RenderingEngine/GenericViewport/Volume3D/viewport3D.js +15 -1
  22. package/dist/esm/RenderingEngine/GenericViewport/genericViewportReferenceCompatibility.js +26 -4
  23. package/dist/esm/RenderingEngine/helpers/planarImageRendering.d.ts +5 -1
  24. package/dist/esm/RenderingEngine/helpers/planarImageRendering.js +19 -5
  25. package/dist/esm/RenderingEngine/helpers/stats/RenderModesPanel.d.ts +4 -0
  26. package/dist/esm/RenderingEngine/helpers/stats/RenderModesPanel.js +48 -1
  27. package/dist/esm/RenderingEngine/helpers/stats/StatsOverlay.js +66 -9
  28. package/dist/esm/init.js +1 -1
  29. package/dist/esm/types/IGenericViewport.d.ts +24 -0
  30. package/dist/esm/types/IGenericViewport.js +0 -0
  31. package/dist/esm/types/ScalingDescriptor.d.ts +5 -0
  32. package/dist/esm/types/ScalingDescriptor.js +0 -0
  33. package/dist/esm/types/index.d.ts +4 -1
  34. package/dist/esm/utilities/VoxelManager.d.ts +2 -0
  35. package/dist/esm/utilities/VoxelManager.js +12 -1
  36. package/dist/esm/utilities/getScalingDescriptor.d.ts +10 -0
  37. package/dist/esm/utilities/getScalingDescriptor.js +41 -0
  38. package/dist/esm/utilities/getTargetVolumeAndSpacingInNormalDir.js +4 -5
  39. package/dist/esm/utilities/getViewportModality.js +5 -14
  40. package/dist/esm/utilities/index.d.ts +4 -2
  41. package/dist/esm/utilities/index.js +5 -2
  42. package/dist/esm/utilities/isPTPrescaledWithSUV.d.ts +1 -1
  43. package/dist/esm/utilities/isPTPrescaledWithSUV.js +1 -1
  44. package/dist/esm/utilities/resolveGenericViewportVolumeId.d.ts +2 -0
  45. package/dist/esm/utilities/resolveGenericViewportVolumeId.js +12 -0
  46. package/dist/esm/utilities/viewportCapabilities.d.ts +10 -1
  47. package/dist/esm/utilities/viewportCapabilities.js +22 -0
  48. package/dist/esm/version.d.ts +1 -1
  49. package/dist/esm/version.js +1 -1
  50. 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.removeAllData();
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 } = getPlanarViewStateCanvasDimensions({
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 } = getPlanarViewStateCanvasDimensions({
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: number;
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
- triggerPlanarNewImage(ctx, {
42
- image: payload.image,
43
- imageIdIndex: payload.initialImageIdIndex,
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
- const imageData = createVTKImageDataFromImage(image);
219
- mapper.setInputData(imageData);
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
- triggerPlanarNewImage(ctx, { image, imageIdIndex });
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
  }
@@ -18,5 +18,6 @@ export declare function triggerPlanarVolumeNewImage(ctx: PlanarImageEventContext
18
18
  acquisitionOrientation?: PlanarViewState['orientation'];
19
19
  imageIds: string[];
20
20
  imageIdIndex: number | undefined;
21
+ maxImageIdIndex: number;
21
22
  }): void;
22
23
  export {};
@@ -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
- triggerPlanarNewImage(ctx);
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 = resolveVolumeOpacityRange(actor, voiRange);
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 delta = Math.max(Math.abs(range.upper - range.lower) * 0.001, 1e-3);
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;
@@ -288,6 +288,9 @@ class VideoViewport extends GenericViewport {
288
288
  return;
289
289
  }
290
290
  }
291
+ customRenderViewportToCanvas() {
292
+ this.render();
293
+ }
291
294
  resetViewState() {
292
295
  if (this.isDestroyed) {
293
296
  return false;
@@ -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(): boolean;
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 imageURIEntries = (context.imageIds || []).map((imageId) => getImageIdCompatibleURIs(imageId));
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 imageURIEntries) {
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 context.imageURIs || []) {
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
- return uniqueStrings([imageIdToURI(imageId), getLegacyImageIdURI(imageId)]);
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(':');