@cornerstonejs/core 1.71.4 → 1.71.6

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 (57) hide show
  1. package/dist/cjs/RenderingEngine/BaseVolumeViewport.d.ts +5 -5
  2. package/dist/cjs/RenderingEngine/BaseVolumeViewport.js +83 -10
  3. package/dist/cjs/RenderingEngine/BaseVolumeViewport.js.map +1 -1
  4. package/dist/cjs/RenderingEngine/StackViewport.d.ts +5 -5
  5. package/dist/cjs/RenderingEngine/StackViewport.js +71 -50
  6. package/dist/cjs/RenderingEngine/StackViewport.js.map +1 -1
  7. package/dist/cjs/RenderingEngine/Viewport.d.ts +2 -1
  8. package/dist/cjs/RenderingEngine/Viewport.js +21 -16
  9. package/dist/cjs/RenderingEngine/Viewport.js.map +1 -1
  10. package/dist/cjs/RenderingEngine/VolumeViewport.d.ts +3 -2
  11. package/dist/cjs/RenderingEngine/VolumeViewport.js +13 -14
  12. package/dist/cjs/RenderingEngine/VolumeViewport.js.map +1 -1
  13. package/dist/cjs/types/IViewport.d.ts +5 -1
  14. package/dist/cjs/utilities/index.d.ts +3 -2
  15. package/dist/cjs/utilities/index.js +8 -4
  16. package/dist/cjs/utilities/index.js.map +1 -1
  17. package/dist/cjs/utilities/isEqual.d.ts +3 -0
  18. package/dist/cjs/utilities/isEqual.js +8 -0
  19. package/dist/cjs/utilities/isEqual.js.map +1 -1
  20. package/dist/esm/RenderingEngine/BaseVolumeViewport.js +83 -13
  21. package/dist/esm/RenderingEngine/BaseVolumeViewport.js.map +1 -1
  22. package/dist/esm/RenderingEngine/StackViewport.js +53 -34
  23. package/dist/esm/RenderingEngine/StackViewport.js.map +1 -1
  24. package/dist/esm/RenderingEngine/Viewport.js +20 -16
  25. package/dist/esm/RenderingEngine/Viewport.js.map +1 -1
  26. package/dist/esm/RenderingEngine/VolumeViewport.js +13 -14
  27. package/dist/esm/RenderingEngine/VolumeViewport.js.map +1 -1
  28. package/dist/esm/utilities/index.js +3 -2
  29. package/dist/esm/utilities/index.js.map +1 -1
  30. package/dist/esm/utilities/isEqual.js +5 -0
  31. package/dist/esm/utilities/isEqual.js.map +1 -1
  32. package/dist/types/RenderingEngine/BaseVolumeViewport.d.ts +5 -5
  33. package/dist/types/RenderingEngine/BaseVolumeViewport.d.ts.map +1 -1
  34. package/dist/types/RenderingEngine/StackViewport.d.ts +5 -5
  35. package/dist/types/RenderingEngine/StackViewport.d.ts.map +1 -1
  36. package/dist/types/RenderingEngine/Viewport.d.ts +2 -1
  37. package/dist/types/RenderingEngine/Viewport.d.ts.map +1 -1
  38. package/dist/types/RenderingEngine/VolumeViewport.d.ts +3 -2
  39. package/dist/types/RenderingEngine/VolumeViewport.d.ts.map +1 -1
  40. package/dist/types/types/IViewport.d.ts +5 -1
  41. package/dist/types/types/IViewport.d.ts.map +1 -1
  42. package/dist/types/types/displayArea.d.ts.map +1 -1
  43. package/dist/types/utilities/index.d.ts +3 -2
  44. package/dist/types/utilities/index.d.ts.map +1 -1
  45. package/dist/types/utilities/isEqual.d.ts +3 -0
  46. package/dist/types/utilities/isEqual.d.ts.map +1 -1
  47. package/dist/umd/index.js +1 -1
  48. package/dist/umd/index.js.map +1 -1
  49. package/package.json +2 -2
  50. package/src/RenderingEngine/BaseVolumeViewport.ts +182 -19
  51. package/src/RenderingEngine/StackViewport.ts +81 -50
  52. package/src/RenderingEngine/Viewport.ts +37 -24
  53. package/src/RenderingEngine/VolumeViewport.ts +40 -25
  54. package/src/types/IViewport.ts +64 -18
  55. package/src/types/displayArea.ts +27 -0
  56. package/src/utilities/index.ts +6 -2
  57. package/src/utilities/isEqual.ts +27 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cornerstonejs/core",
3
- "version": "1.71.4",
3
+ "version": "1.71.6",
4
4
  "description": "",
5
5
  "main": "src/index.ts",
6
6
  "types": "dist/types/index.d.ts",
@@ -47,5 +47,5 @@
47
47
  "type": "individual",
48
48
  "url": "https://ohif.org/donate"
49
49
  },
50
- "gitHead": "c80a68cc56c7e2c96168051a6e2b8d83dab91cc0"
50
+ "gitHead": "8abef807de438a043258b10395c516fa0cd0541d"
51
51
  }
@@ -37,11 +37,12 @@ import type {
37
37
  VolumeViewportProperties,
38
38
  ViewReferenceSpecifier,
39
39
  ReferenceCompatibleOptions,
40
+ ViewPresentation,
41
+ ViewReference,
42
+ IVolumeViewport,
40
43
  } from '../types';
41
44
  import { VoiModifiedEventDetail } from '../types/EventTypes';
42
45
  import type { ViewportInput } from '../types/IViewport';
43
- import type IVolumeViewport from '../types/IVolumeViewport';
44
- import type { ViewReference } from '../types/IViewport';
45
46
  import {
46
47
  actorIsA,
47
48
  applyPreset,
@@ -51,6 +52,10 @@ import {
51
52
  invertRgbTransferFunction,
52
53
  triggerEvent,
53
54
  colormap as colormapUtils,
55
+ isEqualNegative,
56
+ getVolumeViewportScrollInfo,
57
+ snapFocalPointToSlice,
58
+ isEqual,
54
59
  } from '../utilities';
55
60
  import { createVolumeActor } from './helpers';
56
61
  import volumeNewImageEventDispatcher, {
@@ -600,15 +605,36 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
600
605
  viewRefSpecifier: ViewReferenceSpecifier = {}
601
606
  ): ViewReference {
602
607
  const target = super.getViewReference(viewRefSpecifier);
608
+ const volumeId = this.getVolumeId(viewRefSpecifier);
603
609
  if (viewRefSpecifier?.forFrameOfReference !== false) {
604
- target.volumeId = this.getVolumeId(viewRefSpecifier);
605
- }
606
- // TODO - add referencedImageId as a base URL for an image to allow a generic
607
- // method to specify which volumes this should apply to.
608
- return {
609
- ...target,
610
- sliceIndex: this.getCurrentImageIdIndex(),
611
- };
610
+ target.volumeId = volumeId;
611
+ }
612
+ if (typeof viewRefSpecifier?.sliceIndex !== 'number') {
613
+ return target;
614
+ }
615
+ const { viewPlaneNormal } = target;
616
+ const delta =
617
+ (viewRefSpecifier.sliceIndex as number) - this.getCurrentImageIdIndex();
618
+ // Calculate a camera focal point and position
619
+ const { sliceRangeInfo } = getVolumeViewportScrollInfo(
620
+ this,
621
+ volumeId,
622
+ true
623
+ );
624
+
625
+ const { sliceRange, spacingInNormalDirection, camera } = sliceRangeInfo;
626
+ const { focalPoint, position } = camera;
627
+ const { newFocalPoint } = snapFocalPointToSlice(
628
+ focalPoint,
629
+ position,
630
+ sliceRange,
631
+ viewPlaneNormal,
632
+ spacingInNormalDirection,
633
+ delta
634
+ );
635
+ target.cameraFocalPoint = newFocalPoint;
636
+
637
+ return target;
612
638
  }
613
639
 
614
640
  /**
@@ -622,6 +648,9 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
622
648
  viewRef: ViewReference,
623
649
  options?: ReferenceCompatibleOptions
624
650
  ): boolean {
651
+ if (!viewRef.FrameOfReferenceUID) {
652
+ return false;
653
+ }
625
654
  if (!super.isReferenceViewable(viewRef, options)) {
626
655
  return false;
627
656
  }
@@ -638,6 +667,117 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
638
667
  return sliceIndex === undefined || sliceIndex === currentSliceIndex;
639
668
  }
640
669
 
670
+ /**
671
+ * Scrolls the viewport in the given direction/amount
672
+ */
673
+ public scroll(delta = 1) {
674
+ const volumeId = this.getVolumeId();
675
+ const { sliceRangeInfo } = getVolumeViewportScrollInfo(
676
+ this,
677
+ volumeId,
678
+ true
679
+ );
680
+
681
+ if (!sliceRangeInfo) {
682
+ return;
683
+ }
684
+
685
+ const { sliceRange, spacingInNormalDirection, camera } = sliceRangeInfo;
686
+ const { focalPoint, viewPlaneNormal, position } = camera;
687
+
688
+ const { newFocalPoint, newPosition } = snapFocalPointToSlice(
689
+ focalPoint,
690
+ position,
691
+ sliceRange,
692
+ viewPlaneNormal,
693
+ spacingInNormalDirection,
694
+ delta
695
+ );
696
+
697
+ this.setCamera({
698
+ focalPoint: newFocalPoint,
699
+ position: newPosition,
700
+ });
701
+ }
702
+
703
+ /**
704
+ * Navigates to the specified view reference.
705
+ */
706
+ public setViewReference(viewRef: ViewReference): void {
707
+ if (!viewRef) {
708
+ return;
709
+ }
710
+ const volumeId = this.getVolumeId();
711
+ const {
712
+ viewPlaneNormal: refViewPlaneNormal,
713
+ FrameOfReferenceUID: refFrameOfReference,
714
+ cameraFocalPoint,
715
+ viewUp,
716
+ } = viewRef;
717
+ let { sliceIndex } = viewRef;
718
+ const { focalPoint, viewPlaneNormal, position } = this.getCamera();
719
+ const isNegativeNormal = isEqualNegative(
720
+ viewPlaneNormal,
721
+ refViewPlaneNormal
722
+ );
723
+ const isSameNormal = isEqual(viewPlaneNormal, refViewPlaneNormal);
724
+
725
+ // Handle slices
726
+ if (
727
+ typeof sliceIndex === 'number' &&
728
+ viewRef.volumeId === volumeId &&
729
+ (isNegativeNormal || isSameNormal)
730
+ ) {
731
+ const { currentStepIndex, sliceRangeInfo, numScrollSteps } =
732
+ getVolumeViewportScrollInfo(this, volumeId, true);
733
+
734
+ const { sliceRange, spacingInNormalDirection } = sliceRangeInfo;
735
+ if (isNegativeNormal) {
736
+ // Convert opposite orientation view refs to normal orientation
737
+ sliceIndex = numScrollSteps - sliceIndex - 1;
738
+ }
739
+ const delta = sliceIndex - currentStepIndex;
740
+ const { newFocalPoint, newPosition } = snapFocalPointToSlice(
741
+ focalPoint,
742
+ position,
743
+ sliceRange,
744
+ viewPlaneNormal,
745
+ spacingInNormalDirection,
746
+ delta
747
+ );
748
+ this.setCamera({ focalPoint: newFocalPoint, position: newPosition });
749
+ } else if (refFrameOfReference === this.getFrameOfReferenceUID()) {
750
+ // Handle same frame of reference navigation
751
+
752
+ if (refViewPlaneNormal && !isNegativeNormal && !isSameNormal) {
753
+ // Need to update the orientation vectors correctly for this case
754
+ // this.setCameraNoEvent({ viewPlaneNormal: refViewPlaneNormal, viewUp });
755
+ this.setOrientation({ viewPlaneNormal: refViewPlaneNormal, viewUp });
756
+ return this.setViewReference(viewRef);
757
+ }
758
+ if (cameraFocalPoint) {
759
+ const focalDelta = vec3.subtract(
760
+ [0, 0, 0],
761
+ cameraFocalPoint,
762
+ focalPoint
763
+ );
764
+ const useNormal = refViewPlaneNormal ?? viewPlaneNormal;
765
+ const normalDot = vec3.dot(focalDelta, useNormal);
766
+ if (!isEqual(normalDot, 0)) {
767
+ // Gets the portion of the focal point in the normal direction
768
+ vec3.scale(focalDelta, useNormal, normalDot);
769
+ }
770
+ const newFocal = <Point3>vec3.add([0, 0, 0], focalPoint, focalDelta);
771
+ const newPosition = <Point3>vec3.add([0, 0, 0], position, focalDelta);
772
+ this.setCamera({ focalPoint: newFocal, position: newPosition });
773
+ }
774
+ } else {
775
+ throw new Error(
776
+ `Incompatible view refs: ${refFrameOfReference}!==${this.getFrameOfReferenceUID()}`
777
+ );
778
+ }
779
+ }
780
+
641
781
  /**
642
782
  * Sets the properties for the volume viewport on the volume
643
783
  * and if setProperties is called for the first time, the properties will also become the default one.
@@ -1087,7 +1227,10 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
1087
1227
  * @param orientation - The orientation to set the camera to.
1088
1228
  * @param immediate - Whether the `Viewport` should be rendered as soon as the camera is set.
1089
1229
  */
1090
- public setOrientation(orientation: OrientationAxis, immediate = true): void {
1230
+ public setOrientation(
1231
+ _orientation: OrientationAxis | OrientationVectors,
1232
+ _immediate = true
1233
+ ): void {
1091
1234
  console.warn('Method "setOrientation" needs implementation');
1092
1235
  }
1093
1236
 
@@ -1567,21 +1710,40 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
1567
1710
 
1568
1711
  abstract getCurrentImageId(): string;
1569
1712
 
1570
- /** Gets the volumeId to use for references */
1571
- protected getVolumeId(specifier: ViewReferenceSpecifier) {
1713
+ /**
1714
+ * Gets the volumeId to use for references.
1715
+ * Returns undefined if the specified volume is NOT in this viewport.
1716
+ */
1717
+ protected getVolumeId(specifier?: ViewReferenceSpecifier) {
1718
+ const actorEntries = this.getActors();
1719
+ if (!actorEntries) {
1720
+ return;
1721
+ }
1572
1722
  if (!specifier?.volumeId) {
1573
- const actorEntries = this.getActors();
1574
- if (!actorEntries) {
1575
- return;
1576
- }
1577
1723
  // find the first image actor of instance type vtkVolume
1578
1724
  return actorEntries.find(
1579
1725
  (actorEntry) => actorEntry.actor.getClassName() === 'vtkVolume'
1580
1726
  )?.uid;
1581
1727
  }
1582
- return specifier.volumeId;
1728
+
1729
+ // See if this volumeId can be found in one of the actors for this
1730
+ // viewport. This check will cause undefined to be returned when the
1731
+ // volumeId isn't currently shown in this viewport.
1732
+ return actorEntries.find(
1733
+ (actorEntry) =>
1734
+ actorEntry.actor.getClassName() === 'vtkVolume' &&
1735
+ actorEntry.uid === specifier.volumeId
1736
+ )?.uid;
1583
1737
  }
1584
1738
 
1739
+ /**
1740
+ * For a volume viewport, the reference id will be a URN starting with
1741
+ * `volumeId:<volumeId>`, followed by additional arguments to specify
1742
+ * the view orientation. This will end up being a unique string that
1743
+ * identifies the view reference being shown. It is different from the
1744
+ * view reference in that the values are all incorporated into a string to
1745
+ * allow using it as a parameter key.
1746
+ */
1585
1747
  public getReferenceId(specifier: ViewReferenceSpecifier = {}): string {
1586
1748
  let { volumeId, sliceIndex: sliceIndex } = specifier;
1587
1749
  if (!volumeId) {
@@ -1595,7 +1757,8 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
1595
1757
  )?.uid;
1596
1758
  }
1597
1759
 
1598
- sliceIndex ??= this.getCurrentImageIdIndex();
1760
+ const currentIndex = this.getCurrentImageIdIndex();
1761
+ sliceIndex ??= currentIndex;
1599
1762
  const { viewPlaneNormal, focalPoint } = this.getCamera();
1600
1763
  const querySeparator = volumeId.indexOf('?') > -1 ? '&' : '?';
1601
1764
  return `volumeId:${volumeId}${querySeparator}sliceIndex=${sliceIndex}&viewPlaneNormal=${viewPlaneNormal.join(
@@ -511,34 +511,13 @@ class StackViewport extends Viewport implements IStackViewport, IImagesLoader {
511
511
  * metadata, it returns undefined, otherwise, frameOfReferenceUID is returned.
512
512
  * @returns frameOfReferenceUID : string representing frame of reference id
513
513
  */
514
- public getFrameOfReferenceUID = (): string | undefined => {
515
- // Get the current image that is displayed in the viewport
516
- const imageId = this.getCurrentImageId();
517
-
518
- if (!imageId) {
519
- return;
520
- }
521
-
522
- // Use the metadata provider to grab its imagePlaneModule metadata
523
- const imagePlaneModule = metaData.get('imagePlaneModule', imageId);
524
-
525
- // If nothing exists, return undefined
526
- if (!imagePlaneModule) {
527
- return;
528
- }
529
-
530
- // Otherwise, provide the FrameOfReferenceUID so we can map
531
- // annotations made on VolumeViewports back to StackViewports
532
- // and vice versa
533
- return imagePlaneModule.frameOfReferenceUID;
534
- };
514
+ public getFrameOfReferenceUID = (sliceIndex?: number): string =>
515
+ this.getImagePlaneReferenceData(sliceIndex)?.FrameOfReferenceUID;
535
516
 
536
517
  /**
537
518
  * Returns the raw/loaded image being shown inside the stack viewport.
538
519
  */
539
- public getCornerstoneImage = (): IImage => {
540
- return this.csImage;
541
- };
520
+ public getCornerstoneImage = (): IImage => this.csImage;
542
521
 
543
522
  /**
544
523
  * Creates imageMapper based on the provided vtkImageData and also creates
@@ -1609,6 +1588,37 @@ class StackViewport extends Viewport implements IStackViewport, IImagesLoader {
1609
1588
  };
1610
1589
  }
1611
1590
 
1591
+ /**
1592
+ * Gets the view reference data for a given image slice. This uses the
1593
+ * image plane module to read a default focal point/normal, and also returns
1594
+ * the referenced image id and the frame of reference uid.
1595
+ */
1596
+ public getImagePlaneReferenceData(
1597
+ sliceIndex = this.getCurrentImageIdIndex()
1598
+ ): ViewReference {
1599
+ const imageId = this.imageIds[sliceIndex];
1600
+ if (!imageId) {
1601
+ return;
1602
+ }
1603
+ const imagePlaneModule = metaData.get(MetadataModules.IMAGE_PLANE, imageId);
1604
+ const { imagePositionPatient, frameOfReferenceUID: FrameOfReferenceUID } =
1605
+ imagePlaneModule;
1606
+ let { rowCosines, columnCosines } = imagePlaneModule;
1607
+ // Values are null, not undefined, so need to assign instead of defaulting
1608
+ rowCosines ||= [1, 0, 0];
1609
+ columnCosines ||= [0, 1, 0];
1610
+ const viewPlaneNormal = <Point3>(
1611
+ vec3.cross([0, 0, 0], columnCosines, rowCosines)
1612
+ );
1613
+ return {
1614
+ FrameOfReferenceUID,
1615
+ viewPlaneNormal,
1616
+ cameraFocalPoint: <Point3>imagePositionPatient,
1617
+ referencedImageId: imageId,
1618
+ sliceIndex,
1619
+ };
1620
+ }
1621
+
1612
1622
  /**
1613
1623
  * Converts the image direction to camera viewUp and viewplaneNormal
1614
1624
  *
@@ -2215,7 +2225,7 @@ class StackViewport extends Viewport implements IStackViewport, IImagesLoader {
2215
2225
  *
2216
2226
  * @param stackInputs - An array of stack inputs, each containing an image ID and an actor UID.
2217
2227
  */
2218
- public async addImages(stackInputs: Array<IStackInput>): Promise<void> {
2228
+ public addImages(stackInputs: Array<IStackInput>) {
2219
2229
  const actors = this.getActors();
2220
2230
  stackInputs.forEach((stackInput) => {
2221
2231
  const image = cache.getImage(stackInput.imageId);
@@ -2870,10 +2880,6 @@ class StackViewport extends Viewport implements IStackViewport, IImagesLoader {
2870
2880
  return this.currentImageIdIndex;
2871
2881
  };
2872
2882
 
2873
- public getSliceIndex = (): number => {
2874
- return this.currentImageIdIndex;
2875
- };
2876
-
2877
2883
  /**
2878
2884
  * Checks to see if this target is or could be shown in this viewport
2879
2885
  */
@@ -2910,40 +2916,65 @@ class StackViewport extends Viewport implements IStackViewport, IImagesLoader {
2910
2916
 
2911
2917
  /**
2912
2918
  * Gets a standard target to show this image instance.
2919
+ * Returns undefined if the requested slice index is not available.
2920
+ *
2921
+ * <b>Warning<b>If using sliceIndex for requeseting a specific reference, the slice index MUST come
2922
+ * from the stack of image ids. Using slice index from a volume or from a different
2923
+ * stack of images ids, EVEN if they contain the same set of images will result in
2924
+ * random images being chosen.
2913
2925
  */
2914
2926
  public getViewReference(
2915
2927
  viewRefSpecifier: ViewReferenceSpecifier = {}
2916
2928
  ): ViewReference {
2917
- const { sliceIndex: sliceIndex = this.currentImageIdIndex } =
2918
- viewRefSpecifier;
2919
- return {
2920
- ...super.getViewReference(viewRefSpecifier),
2921
- referencedImageId: `${this.imageIds[sliceIndex as number]}`,
2922
- sliceIndex: sliceIndex,
2923
- };
2929
+ const { sliceIndex = this.getCurrentImageIdIndex() } = viewRefSpecifier;
2930
+ const reference = super.getViewReference(viewRefSpecifier);
2931
+ const referencedImageId = this.imageIds[sliceIndex as number];
2932
+ if (!referencedImageId) {
2933
+ return;
2934
+ }
2935
+ reference.referencedImageId = referencedImageId;
2936
+ if (this.getCurrentImageIdIndex() !== sliceIndex) {
2937
+ const referenceData = this.getImagePlaneReferenceData(
2938
+ sliceIndex as number
2939
+ );
2940
+ if (!referenceData) {
2941
+ return;
2942
+ }
2943
+ Object.assign(reference, referenceData);
2944
+ }
2945
+ return reference;
2924
2946
  }
2925
2947
 
2926
2948
  /**
2927
2949
  * Applies the view reference, which may navigate the slice index and apply
2928
- * other camera modifications
2950
+ * other camera modifications.
2951
+ * Assumes that the slice index is correct for this viewport
2929
2952
  */
2930
- public setView(viewRef?: ViewReference, viewPres?: ViewPresentation): void {
2931
- const camera = this.getCamera();
2932
- super.setView(viewRef, viewPres);
2933
- if (viewRef) {
2934
- const { viewPlaneNormal, sliceIndex } = viewRef;
2935
- if (
2936
- viewPlaneNormal &&
2937
- !isEqual(viewPlaneNormal, camera.viewPlaneNormal)
2938
- ) {
2939
- return;
2940
- }
2941
- if (sliceIndex || sliceIndex === 0) {
2942
- this.setImageIdIndex(sliceIndex as number);
2953
+ public setViewReference(viewRef: ViewReference): void {
2954
+ if (!viewRef) {
2955
+ return;
2956
+ }
2957
+ const { referencedImageId, sliceIndex, volumeId } = viewRef;
2958
+ if (
2959
+ typeof sliceIndex === 'number' &&
2960
+ referencedImageId &&
2961
+ referencedImageId === this.imageIds[sliceIndex]
2962
+ ) {
2963
+ this.setImageIdIndex(sliceIndex);
2964
+ } else {
2965
+ const foundIndex = this.imageIds.indexOf(referencedImageId);
2966
+ if (foundIndex !== -1) {
2967
+ this.setImageIdIndex(foundIndex);
2968
+ } else {
2969
+ throw new Error('Unsupported - referenced image id not found');
2943
2970
  }
2944
2971
  }
2945
2972
  }
2946
2973
 
2974
+ /**
2975
+ * Returns the imageId string for the specified view, using the
2976
+ * `imageId:<imageId>` URN format.
2977
+ */
2947
2978
  public getReferenceId(specifier: ViewReferenceSpecifier = {}): string {
2948
2979
  const { sliceIndex: sliceIndex = this.currentImageIdIndex } = specifier;
2949
2980
  if (Array.isArray(sliceIndex)) {
@@ -695,7 +695,7 @@ class Viewport implements IViewport {
695
695
  this.setDisplayAreaScale(displayArea);
696
696
  } else {
697
697
  this.setInterpolationType(
698
- this.getProperties().interpolationType || InterpolationType.LINEAR
698
+ this.getProperties()?.interpolationType || InterpolationType.LINEAR
699
699
  );
700
700
  this.setDisplayAreaFit(displayArea);
701
701
  }
@@ -1097,6 +1097,11 @@ class Viewport implements IViewport {
1097
1097
  throw new Error('Not implemented');
1098
1098
  }
1099
1099
 
1100
+ /**
1101
+ * Gets a referenced image url of some sort - could be a real image id, or
1102
+ * could be a URL with parameters. Regardless it refers to the currently displaying
1103
+ * image as a string value.
1104
+ */
1100
1105
  public getReferenceId(_specifier?: ViewReferenceSpecifier): string {
1101
1106
  return null;
1102
1107
  }
@@ -1592,11 +1597,16 @@ class Viewport implements IViewport {
1592
1597
  public getViewReference(
1593
1598
  viewRefSpecifier: ViewReferenceSpecifier = {}
1594
1599
  ): ViewReference {
1595
- const { focalPoint: cameraFocalPoint, viewPlaneNormal } = this.getCamera();
1600
+ const {
1601
+ focalPoint: cameraFocalPoint,
1602
+ viewPlaneNormal,
1603
+ viewUp,
1604
+ } = this.getCamera();
1596
1605
  const target: ViewReference = {
1597
1606
  FrameOfReferenceUID: this.getFrameOfReferenceUID(),
1598
1607
  cameraFocalPoint,
1599
1608
  viewPlaneNormal,
1609
+ viewUp,
1600
1610
  sliceIndex: viewRefSpecifier.sliceIndex ?? this.getCurrentImageIdIndex(),
1601
1611
  };
1602
1612
  return target;
@@ -1630,8 +1640,8 @@ class Viewport implements IViewport {
1630
1640
  viewPlaneNormal
1631
1641
  )
1632
1642
  ) {
1633
- // Could navigate as a volume to the reference
1634
- return options?.asVolume === true;
1643
+ // Could navigate as a volume to the reference with an orientation change
1644
+ return options?.withOrientation === true;
1635
1645
  }
1636
1646
  return true;
1637
1647
  }
@@ -1689,27 +1699,30 @@ class Viewport implements IViewport {
1689
1699
  }
1690
1700
 
1691
1701
  /**
1692
- * Sets the given view. This can apply both the view reference and view presentation
1693
- * without getting multiple event notifications on shared values like camera updates or
1694
- * flickers as multiple changes are applied.
1695
- *
1696
- * @param viewRef - the basic positioning in terms of what image id/slice index/orientation to display
1697
- * * The viewRef must be applicable to the current stack or volume, otherwise an exception will be thrown
1698
- * @param viewPres - the presentation information to apply to the current image (as chosen above)
1702
+ * Navigates to the image specified by the viewRef.
1699
1703
  */
1700
- public setView(viewRef?: ViewReference, viewPres?: ViewPresentation) {
1701
- if (viewPres) {
1702
- const { displayArea, zoom = this.getZoom(), pan, rotation } = viewPres;
1703
- if (displayArea !== this.getDisplayArea()) {
1704
- this.setDisplayArea(displayArea);
1705
- }
1706
- this.setZoom(zoom);
1707
- if (pan) {
1708
- this.setPan(vec2.scale([0, 0], pan, zoom) as Point2);
1709
- }
1710
- if (rotation >= 0) {
1711
- this.setRotation(rotation);
1712
- }
1704
+ public setViewReference(viewRef: ViewReference) {
1705
+ // No-op
1706
+ }
1707
+
1708
+ /**
1709
+ * Applies the display area, zoom, pan and rotation from the view presentation.
1710
+ * No-op is viewPres isn't defined.
1711
+ */
1712
+ public setViewPresentation(viewPres: ViewPresentation) {
1713
+ if (!viewPres) {
1714
+ return;
1715
+ }
1716
+ const { displayArea, zoom = this.getZoom(), pan, rotation } = viewPres;
1717
+ if (displayArea !== this.getDisplayArea()) {
1718
+ this.setDisplayArea(displayArea);
1719
+ }
1720
+ this.setZoom(zoom);
1721
+ if (pan) {
1722
+ this.setPan(vec2.scale([0, 0], pan, zoom) as Point2);
1723
+ }
1724
+ if (rotation >= 0) {
1725
+ this.setRotation(rotation);
1713
1726
  }
1714
1727
  }
1715
1728
 
@@ -1,8 +1,6 @@
1
1
  import vtkPlane from '@kitware/vtk.js/Common/DataModel/Plane';
2
2
  import vtkVolume from '@kitware/vtk.js/Rendering/Core/Volume';
3
3
 
4
- import { vec3 } from 'gl-matrix';
5
-
6
4
  import cache from '../cache';
7
5
  import { MPR_CAMERA_VALUES, RENDERING_DEFAULTS } from '../constants';
8
6
  import { BlendModes, OrientationAxis, Events } from '../enums';
@@ -13,6 +11,8 @@ import type {
13
11
  OrientationVectors,
14
12
  Point3,
15
13
  EventTypes,
14
+ ViewReference,
15
+ ViewReferenceSpecifier,
16
16
  } from '../types';
17
17
  import type { ViewportInput } from '../types/IViewport';
18
18
  import {
@@ -29,6 +29,7 @@ import setDefaultVolumeVOI from './helpers/setDefaultVolumeVOI';
29
29
  import { setTransferFunctionNodes } from '../utilities/transferFunctionUtils';
30
30
  import { ImageActor } from '../types/IActor';
31
31
  import getImageSliceDataForVolumeViewport from '../utilities/getImageSliceDataForVolumeViewport';
32
+ import getVolumeViewportScrollInfo from '../utilities/getVolumeViewportScrollInfo';
32
33
 
33
34
  /**
34
35
  * An object representing a VolumeViewport. VolumeViewports are used to render
@@ -267,7 +268,6 @@ class VolumeViewport extends BaseVolumeViewport {
267
268
 
268
269
  const activeCamera = this.getVtkActiveCamera();
269
270
  const viewPlaneNormal = <Point3>activeCamera.getViewPlaneNormal();
270
- const viewUp = <Point3>activeCamera.getViewUp();
271
271
  const focalPoint = <Point3>activeCamera.getFocalPoint();
272
272
 
273
273
  // always add clipping planes for the volume viewport. If a use case
@@ -365,6 +365,10 @@ class VolumeViewport extends BaseVolumeViewport {
365
365
  }
366
366
 
367
367
  /**
368
+ * Uses the origin and focalPoint to calculate the slice index.
369
+
370
+
371
+
368
372
  * Resets the slab thickness of the actors of the viewport to the default value.
369
373
  */
370
374
  public resetSlabThickness(): void {
@@ -383,32 +387,23 @@ class VolumeViewport extends BaseVolumeViewport {
383
387
  }
384
388
 
385
389
  /**
386
- * Uses the origin and focalPoint to calculate the slice index.
390
+ * Uses the slice range information to compute the current image id index.
391
+ * Note that this may be offset from the origin location, or opposite in
392
+ * direction to the distance from the origin location, as the index is a
393
+ * complete index from minimum to maximum.
387
394
  *
388
395
  * @returns The slice index in the direction of the view
389
396
  */
390
- public getCurrentImageIdIndex = (volumeId?: string): number => {
391
- const { viewPlaneNormal, focalPoint } = this.getCamera();
392
-
393
- const imageData = this.getImageData(volumeId);
394
-
395
- if (!imageData) {
396
- return;
397
- }
398
-
399
- const { origin, direction, spacing } = imageData;
400
-
401
- const spacingInNormal = getSpacingInNormalDirection(
402
- { direction, spacing },
403
- viewPlaneNormal
397
+ public getCurrentImageIdIndex = (
398
+ volumeId?: string,
399
+ useSlabThickness = true
400
+ ): number => {
401
+ const { currentStepIndex } = getVolumeViewportScrollInfo(
402
+ this,
403
+ volumeId || this.getVolumeId(),
404
+ useSlabThickness
404
405
  );
405
- const sub = vec3.create();
406
- vec3.sub(sub, focalPoint, origin);
407
- const distance = vec3.dot(sub, viewPlaneNormal);
408
-
409
- // divide by the spacing in the normal direction to get the
410
- // number of steps, and subtract 1 to get the index
411
- return Math.round(Math.abs(distance) / spacingInNormal);
406
+ return currentStepIndex;
412
407
  };
413
408
 
414
409
  /**
@@ -438,6 +433,26 @@ class VolumeViewport extends BaseVolumeViewport {
438
433
  return getClosestImageId(volume, focalPoint, viewPlaneNormal);
439
434
  };
440
435
 
436
+ /**
437
+ * Gets a view target, allowing comparison between view positions as well
438
+ * as restoring views later.
439
+ * Add the referenced image id.
440
+ */
441
+ public getViewReference(
442
+ viewRefSpecifier: ViewReferenceSpecifier = {}
443
+ ): ViewReference {
444
+ const viewRef = super.getViewReference(viewRefSpecifier);
445
+ if (!viewRef?.volumeId) {
446
+ return;
447
+ }
448
+ const volume = cache.getVolume(viewRef.volumeId);
449
+ viewRef.referencedImageId = getClosestImageId(
450
+ volume,
451
+ viewRef.cameraFocalPoint,
452
+ viewRef.viewPlaneNormal
453
+ );
454
+ return viewRef;
455
+ }
441
456
  /**
442
457
  * Reset the viewport properties to the default values
443
458
  *