@cornerstonejs/core 1.71.3 → 1.71.5

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 (79) hide show
  1. package/dist/cjs/RenderingEngine/BaseVolumeViewport.d.ts +7 -6
  2. package/dist/cjs/RenderingEngine/BaseVolumeViewport.js +87 -12
  3. package/dist/cjs/RenderingEngine/BaseVolumeViewport.js.map +1 -1
  4. package/dist/cjs/RenderingEngine/RenderingEngine.js +6 -1
  5. package/dist/cjs/RenderingEngine/RenderingEngine.js.map +1 -1
  6. package/dist/cjs/RenderingEngine/StackViewport.d.ts +5 -5
  7. package/dist/cjs/RenderingEngine/StackViewport.js +71 -50
  8. package/dist/cjs/RenderingEngine/StackViewport.js.map +1 -1
  9. package/dist/cjs/RenderingEngine/Viewport.d.ts +2 -1
  10. package/dist/cjs/RenderingEngine/Viewport.js +21 -16
  11. package/dist/cjs/RenderingEngine/Viewport.js.map +1 -1
  12. package/dist/cjs/RenderingEngine/VolumeViewport.d.ts +5 -3
  13. package/dist/cjs/RenderingEngine/VolumeViewport.js +39 -15
  14. package/dist/cjs/RenderingEngine/VolumeViewport.js.map +1 -1
  15. package/dist/cjs/RenderingEngine/VolumeViewport3D.d.ts +1 -0
  16. package/dist/cjs/RenderingEngine/VolumeViewport3D.js +3 -0
  17. package/dist/cjs/RenderingEngine/VolumeViewport3D.js.map +1 -1
  18. package/dist/cjs/types/EventTypes.d.ts +8 -1
  19. package/dist/cjs/types/IViewport.d.ts +5 -1
  20. package/dist/cjs/types/IVolumeViewport.d.ts +2 -1
  21. package/dist/cjs/utilities/index.d.ts +3 -2
  22. package/dist/cjs/utilities/index.js +8 -4
  23. package/dist/cjs/utilities/index.js.map +1 -1
  24. package/dist/cjs/utilities/isEqual.d.ts +3 -0
  25. package/dist/cjs/utilities/isEqual.js +8 -0
  26. package/dist/cjs/utilities/isEqual.js.map +1 -1
  27. package/dist/esm/RenderingEngine/BaseVolumeViewport.js +87 -15
  28. package/dist/esm/RenderingEngine/BaseVolumeViewport.js.map +1 -1
  29. package/dist/esm/RenderingEngine/RenderingEngine.js +6 -1
  30. package/dist/esm/RenderingEngine/RenderingEngine.js.map +1 -1
  31. package/dist/esm/RenderingEngine/StackViewport.js +53 -34
  32. package/dist/esm/RenderingEngine/StackViewport.js.map +1 -1
  33. package/dist/esm/RenderingEngine/Viewport.js +20 -16
  34. package/dist/esm/RenderingEngine/Viewport.js.map +1 -1
  35. package/dist/esm/RenderingEngine/VolumeViewport.js +39 -15
  36. package/dist/esm/RenderingEngine/VolumeViewport.js.map +1 -1
  37. package/dist/esm/RenderingEngine/VolumeViewport3D.js +3 -0
  38. package/dist/esm/RenderingEngine/VolumeViewport3D.js.map +1 -1
  39. package/dist/esm/utilities/index.js +3 -2
  40. package/dist/esm/utilities/index.js.map +1 -1
  41. package/dist/esm/utilities/isEqual.js +5 -0
  42. package/dist/esm/utilities/isEqual.js.map +1 -1
  43. package/dist/types/RenderingEngine/BaseVolumeViewport.d.ts +7 -6
  44. package/dist/types/RenderingEngine/BaseVolumeViewport.d.ts.map +1 -1
  45. package/dist/types/RenderingEngine/RenderingEngine.d.ts.map +1 -1
  46. package/dist/types/RenderingEngine/StackViewport.d.ts +5 -5
  47. package/dist/types/RenderingEngine/StackViewport.d.ts.map +1 -1
  48. package/dist/types/RenderingEngine/Viewport.d.ts +2 -1
  49. package/dist/types/RenderingEngine/Viewport.d.ts.map +1 -1
  50. package/dist/types/RenderingEngine/VolumeViewport.d.ts +5 -3
  51. package/dist/types/RenderingEngine/VolumeViewport.d.ts.map +1 -1
  52. package/dist/types/RenderingEngine/VolumeViewport3D.d.ts +1 -0
  53. package/dist/types/RenderingEngine/VolumeViewport3D.d.ts.map +1 -1
  54. package/dist/types/types/EventTypes.d.ts +8 -1
  55. package/dist/types/types/EventTypes.d.ts.map +1 -1
  56. package/dist/types/types/IViewport.d.ts +5 -1
  57. package/dist/types/types/IViewport.d.ts.map +1 -1
  58. package/dist/types/types/IVolumeViewport.d.ts +2 -1
  59. package/dist/types/types/IVolumeViewport.d.ts.map +1 -1
  60. package/dist/types/types/displayArea.d.ts.map +1 -1
  61. package/dist/types/utilities/index.d.ts +3 -2
  62. package/dist/types/utilities/index.d.ts.map +1 -1
  63. package/dist/types/utilities/isEqual.d.ts +3 -0
  64. package/dist/types/utilities/isEqual.d.ts.map +1 -1
  65. package/dist/umd/index.js +1 -1
  66. package/dist/umd/index.js.map +1 -1
  67. package/package.json +2 -2
  68. package/src/RenderingEngine/BaseVolumeViewport.ts +189 -21
  69. package/src/RenderingEngine/RenderingEngine.ts +12 -1
  70. package/src/RenderingEngine/StackViewport.ts +81 -50
  71. package/src/RenderingEngine/Viewport.ts +37 -24
  72. package/src/RenderingEngine/VolumeViewport.ts +73 -24
  73. package/src/RenderingEngine/VolumeViewport3D.ts +4 -0
  74. package/src/types/EventTypes.ts +19 -0
  75. package/src/types/IViewport.ts +64 -18
  76. package/src/types/IVolumeViewport.ts +6 -1
  77. package/src/types/displayArea.ts +27 -0
  78. package/src/utilities/index.ts +6 -2
  79. 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.3",
3
+ "version": "1.71.5",
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": "1f8cced111f519d18a4f832acf9204c8b160d0ab"
50
+ "gitHead": "589239e007a87fa5316efa222054c518e6f00098"
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, {
@@ -126,7 +131,8 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
126
131
  }
127
132
 
128
133
  protected applyViewOrientation(
129
- orientation: OrientationAxis | OrientationVectors
134
+ orientation: OrientationAxis | OrientationVectors,
135
+ resetCamera = true
130
136
  ) {
131
137
  const { viewPlaneNormal, viewUp } =
132
138
  this._getOrientationVectors(orientation);
@@ -139,7 +145,9 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
139
145
  camera.setViewUpFrom(viewUp);
140
146
  this.initialViewUp = viewUp;
141
147
 
142
- this.resetCamera();
148
+ if (resetCamera) {
149
+ this.resetCamera();
150
+ }
143
151
  }
144
152
 
145
153
  private initializeVolumeNewImageEventDispatcher(): void {
@@ -597,15 +605,36 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
597
605
  viewRefSpecifier: ViewReferenceSpecifier = {}
598
606
  ): ViewReference {
599
607
  const target = super.getViewReference(viewRefSpecifier);
608
+ const volumeId = this.getVolumeId(viewRefSpecifier);
600
609
  if (viewRefSpecifier?.forFrameOfReference !== false) {
601
- target.volumeId = this.getVolumeId(viewRefSpecifier);
602
- }
603
- // TODO - add referencedImageId as a base URL for an image to allow a generic
604
- // method to specify which volumes this should apply to.
605
- return {
606
- ...target,
607
- sliceIndex: this.getCurrentImageIdIndex(),
608
- };
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;
609
638
  }
610
639
 
611
640
  /**
@@ -619,6 +648,9 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
619
648
  viewRef: ViewReference,
620
649
  options?: ReferenceCompatibleOptions
621
650
  ): boolean {
651
+ if (!viewRef.FrameOfReferenceUID) {
652
+ return false;
653
+ }
622
654
  if (!super.isReferenceViewable(viewRef, options)) {
623
655
  return false;
624
656
  }
@@ -635,6 +667,117 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
635
667
  return sliceIndex === undefined || sliceIndex === currentSliceIndex;
636
668
  }
637
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
+
638
781
  /**
639
782
  * Sets the properties for the volume viewport on the volume
640
783
  * and if setProperties is called for the first time, the properties will also become the default one.
@@ -1084,7 +1227,10 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
1084
1227
  * @param orientation - The orientation to set the camera to.
1085
1228
  * @param immediate - Whether the `Viewport` should be rendered as soon as the camera is set.
1086
1229
  */
1087
- public setOrientation(orientation: OrientationAxis, immediate = true): void {
1230
+ public setOrientation(
1231
+ _orientation: OrientationAxis | OrientationVectors,
1232
+ _immediate = true
1233
+ ): void {
1088
1234
  console.warn('Method "setOrientation" needs implementation');
1089
1235
  }
1090
1236
 
@@ -1564,21 +1710,40 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
1564
1710
 
1565
1711
  abstract getCurrentImageId(): string;
1566
1712
 
1567
- /** Gets the volumeId to use for references */
1568
- 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
+ }
1569
1722
  if (!specifier?.volumeId) {
1570
- const actorEntries = this.getActors();
1571
- if (!actorEntries) {
1572
- return;
1573
- }
1574
1723
  // find the first image actor of instance type vtkVolume
1575
1724
  return actorEntries.find(
1576
1725
  (actorEntry) => actorEntry.actor.getClassName() === 'vtkVolume'
1577
1726
  )?.uid;
1578
1727
  }
1579
- 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;
1580
1737
  }
1581
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
+ */
1582
1747
  public getReferenceId(specifier: ViewReferenceSpecifier = {}): string {
1583
1748
  let { volumeId, sliceIndex: sliceIndex } = specifier;
1584
1749
  if (!volumeId) {
@@ -1592,7 +1757,8 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
1592
1757
  )?.uid;
1593
1758
  }
1594
1759
 
1595
- sliceIndex ??= this.getCurrentImageIdIndex();
1760
+ const currentIndex = this.getCurrentImageIdIndex();
1761
+ sliceIndex ??= currentIndex;
1596
1762
  const { viewPlaneNormal, focalPoint } = this.getCamera();
1597
1763
  const querySeparator = volumeId.indexOf('?') > -1 ? '&' : '?';
1598
1764
  return `volumeId:${volumeId}${querySeparator}sliceIndex=${sliceIndex}&viewPlaneNormal=${viewPlaneNormal.join(
@@ -1611,6 +1777,8 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
1611
1777
  filterActorUIDs?: Array<string>
1612
1778
  ): void;
1613
1779
 
1780
+ abstract resetSlabThickness(): void;
1781
+
1614
1782
  abstract resetProperties(volumeId?: string): void;
1615
1783
  }
1616
1784
 
@@ -626,7 +626,18 @@ class RenderingEngine implements IRenderingEngine {
626
626
  const prevCamera = vp.getCamera();
627
627
  const rotation = vp.getRotation();
628
628
  const { flipHorizontal } = prevCamera;
629
- vp.resetCamera();
629
+ const resetPan = true;
630
+ const resetZoom = true;
631
+ const resetToCenter = true;
632
+ const resetRotation = false;
633
+ const supressEvents = true;
634
+ vp.resetCamera(
635
+ resetPan,
636
+ resetZoom,
637
+ resetToCenter,
638
+ resetRotation,
639
+ supressEvents
640
+ );
630
641
 
631
642
  const displayArea = vp.getDisplayArea();
632
643
 
@@ -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