@cornerstonejs/core 1.38.1 → 1.40.0

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 (78) hide show
  1. package/dist/cjs/RenderingEngine/BaseVolumeViewport.d.ts +5 -1
  2. package/dist/cjs/RenderingEngine/BaseVolumeViewport.js +47 -2
  3. package/dist/cjs/RenderingEngine/BaseVolumeViewport.js.map +1 -1
  4. package/dist/cjs/RenderingEngine/Viewport.js +2 -3
  5. package/dist/cjs/RenderingEngine/Viewport.js.map +1 -1
  6. package/dist/cjs/RenderingEngine/VolumeViewport.d.ts +1 -2
  7. package/dist/cjs/RenderingEngine/VolumeViewport.js +16 -2
  8. package/dist/cjs/RenderingEngine/VolumeViewport.js.map +1 -1
  9. package/dist/cjs/types/IVolumeViewport.d.ts +1 -1
  10. package/dist/cjs/types/ViewportProperties.d.ts +1 -0
  11. package/dist/cjs/types/VolumeViewportProperties.d.ts +2 -0
  12. package/dist/cjs/utilities/getCurrentVolumeViewportSlice.d.ts +10 -0
  13. package/dist/cjs/utilities/getCurrentVolumeViewportSlice.js +86 -0
  14. package/dist/cjs/utilities/getCurrentVolumeViewportSlice.js.map +1 -0
  15. package/dist/cjs/utilities/index.d.ts +3 -1
  16. package/dist/cjs/utilities/index.js +6 -2
  17. package/dist/cjs/utilities/index.js.map +1 -1
  18. package/dist/cjs/utilities/transformCanvasToIJK.d.ts +2 -0
  19. package/dist/cjs/utilities/transformCanvasToIJK.js +14 -0
  20. package/dist/cjs/utilities/transformCanvasToIJK.js.map +1 -0
  21. package/dist/cjs/utilities/transformIJKToCanvas.d.ts +2 -0
  22. package/dist/cjs/utilities/transformIJKToCanvas.js +14 -0
  23. package/dist/cjs/utilities/transformIJKToCanvas.js.map +1 -0
  24. package/dist/cjs/utilities/transformIndexToWorld.d.ts +2 -0
  25. package/dist/cjs/utilities/transformIndexToWorld.js +7 -0
  26. package/dist/cjs/utilities/transformIndexToWorld.js.map +1 -0
  27. package/dist/esm/RenderingEngine/BaseVolumeViewport.js +47 -2
  28. package/dist/esm/RenderingEngine/BaseVolumeViewport.js.map +1 -1
  29. package/dist/esm/RenderingEngine/Viewport.js +1 -2
  30. package/dist/esm/RenderingEngine/Viewport.js.map +1 -1
  31. package/dist/esm/RenderingEngine/VolumeViewport.js +16 -2
  32. package/dist/esm/RenderingEngine/VolumeViewport.js.map +1 -1
  33. package/dist/esm/utilities/getCurrentVolumeViewportSlice.js +82 -0
  34. package/dist/esm/utilities/getCurrentVolumeViewportSlice.js.map +1 -0
  35. package/dist/esm/utilities/index.js +3 -1
  36. package/dist/esm/utilities/index.js.map +1 -1
  37. package/dist/esm/utilities/transformCanvasToIJK.js +7 -0
  38. package/dist/esm/utilities/transformCanvasToIJK.js.map +1 -0
  39. package/dist/esm/utilities/transformIJKToCanvas.js +7 -0
  40. package/dist/esm/utilities/transformIJKToCanvas.js.map +1 -0
  41. package/dist/esm/utilities/transformIndexToWorld.js +4 -0
  42. package/dist/esm/utilities/transformIndexToWorld.js.map +1 -0
  43. package/dist/types/RenderingEngine/BaseVolumeViewport.d.ts +5 -1
  44. package/dist/types/RenderingEngine/BaseVolumeViewport.d.ts.map +1 -1
  45. package/dist/types/RenderingEngine/Viewport.d.ts.map +1 -1
  46. package/dist/types/RenderingEngine/VolumeViewport.d.ts +1 -2
  47. package/dist/types/RenderingEngine/VolumeViewport.d.ts.map +1 -1
  48. package/dist/types/types/IVolumeViewport.d.ts +1 -1
  49. package/dist/types/types/IVolumeViewport.d.ts.map +1 -1
  50. package/dist/types/types/ViewportProperties.d.ts +1 -0
  51. package/dist/types/types/ViewportProperties.d.ts.map +1 -1
  52. package/dist/types/types/VolumeViewportProperties.d.ts +2 -0
  53. package/dist/types/types/VolumeViewportProperties.d.ts.map +1 -1
  54. package/dist/types/utilities/getCurrentVolumeViewportSlice.d.ts +11 -0
  55. package/dist/types/utilities/getCurrentVolumeViewportSlice.d.ts.map +1 -0
  56. package/dist/types/utilities/index.d.ts +3 -1
  57. package/dist/types/utilities/index.d.ts.map +1 -1
  58. package/dist/types/utilities/transformCanvasToIJK.d.ts +3 -0
  59. package/dist/types/utilities/transformCanvasToIJK.d.ts.map +1 -0
  60. package/dist/types/utilities/transformIJKToCanvas.d.ts +3 -0
  61. package/dist/types/utilities/transformIJKToCanvas.d.ts.map +1 -0
  62. package/dist/types/utilities/transformIndexToWorld.d.ts +3 -0
  63. package/dist/types/utilities/transformIndexToWorld.d.ts.map +1 -0
  64. package/dist/umd/index.js +1 -1
  65. package/dist/umd/index.js.map +1 -1
  66. package/package.json +2 -2
  67. package/src/RenderingEngine/BaseVolumeViewport.ts +99 -1
  68. package/src/RenderingEngine/Viewport.ts +6 -3
  69. package/src/RenderingEngine/VolumeViewport.ts +23 -4
  70. package/src/types/IViewport.ts +1 -1
  71. package/src/types/IVolumeViewport.ts +2 -1
  72. package/src/types/ViewportProperties.ts +2 -0
  73. package/src/types/VolumeViewportProperties.ts +3 -0
  74. package/src/utilities/getCurrentVolumeViewportSlice.ts +166 -0
  75. package/src/utilities/index.ts +4 -0
  76. package/src/utilities/transformCanvasToIJK.ts +18 -0
  77. package/src/utilities/transformIJKToCanvas.ts +18 -0
  78. package/src/utilities/transformIndexToWorld.ts +14 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cornerstonejs/core",
3
- "version": "1.38.1",
3
+ "version": "1.40.0",
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": "1b7cb02f6c11935850f80a5382be21dd8aaab4d5"
50
+ "gitHead": "28ed3eaf75ccd5cab7c7c0df0f4b09950cc42d7c"
51
51
  }
@@ -3,6 +3,8 @@ import vtkColorTransferFunction from '@kitware/vtk.js/Rendering/Core/ColorTransf
3
3
  import vtkColorMaps from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction/ColorMaps';
4
4
  import vtkPiecewiseFunction from '@kitware/vtk.js/Common/DataModel/PiecewiseFunction';
5
5
 
6
+ import { vec3 } from 'gl-matrix';
7
+
6
8
  import cache from '../cache';
7
9
  import {
8
10
  MPR_CAMERA_VALUES,
@@ -31,7 +33,9 @@ import type {
31
33
  Point2,
32
34
  Point3,
33
35
  VOIRange,
36
+ EventTypes,
34
37
  VolumeViewportProperties,
38
+ ICamera,
35
39
  } from '../types';
36
40
  import { VoiModifiedEventDetail } from '../types/EventTypes';
37
41
  import type { ViewportInput } from '../types/IViewport';
@@ -77,7 +81,8 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
77
81
  string,
78
82
  VolumeViewportProperties
79
83
  >();
80
-
84
+ // Camera properties
85
+ private initialViewUp: Point3;
81
86
  protected viewportProperties: VolumeViewportProperties = {};
82
87
 
83
88
  constructor(props: ViewportInput) {
@@ -95,6 +100,16 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
95
100
  const renderer = this.getRenderer();
96
101
 
97
102
  const camera = vtkSlabCamera.newInstance();
103
+
104
+ this.initialViewUp = <Point3>[0, -1, 0];
105
+ const viewPlaneNormal = <Point3>[0, 0, -1];
106
+
107
+ camera.setDirectionOfProjection(
108
+ -viewPlaneNormal[0],
109
+ -viewPlaneNormal[1],
110
+ -viewPlaneNormal[2]
111
+ );
112
+ camera.setViewUp(...this.initialViewUp);
98
113
  renderer.setActiveCamera(camera);
99
114
 
100
115
  switch (this.type) {
@@ -471,6 +486,33 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
471
486
  this.viewportProperties.voiRange = voiRangeToUse;
472
487
  }
473
488
 
489
+ private setRotation(rotation: number): void {
490
+ const previousCamera = this.getCamera();
491
+
492
+ this.rotateCamera(rotation);
493
+
494
+ // New camera after rotation
495
+ const camera = this.getCamera();
496
+
497
+ const eventDetail: EventTypes.CameraModifiedEventDetail = {
498
+ previousCamera,
499
+ camera,
500
+ element: this.element,
501
+ viewportId: this.id,
502
+ renderingEngineId: this.renderingEngineId,
503
+ rotation,
504
+ };
505
+
506
+ triggerEvent(this.element, Events.CAMERA_MODIFIED, eventDetail);
507
+ this.viewportProperties.rotation = rotation;
508
+ }
509
+
510
+ private rotateCamera(rotation: number): void {
511
+ const rotationToApply = rotation - this.getRotation();
512
+ // rotating camera to the new value
513
+ this.getVtkActiveCamera().roll(-rotationToApply);
514
+ }
515
+
474
516
  /**
475
517
  * Update the default properties for the volume viewport on the volume
476
518
  * @param ViewportProperties - The properties to set
@@ -525,6 +567,7 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
525
567
  preset,
526
568
  interpolationType,
527
569
  slabThickness,
570
+ rotation,
528
571
  }: VolumeViewportProperties = {},
529
572
  volumeId?: string,
530
573
  suppressEvents = false
@@ -538,6 +581,7 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
538
581
  colormap,
539
582
  preset,
540
583
  slabThickness,
584
+ rotation,
541
585
  });
542
586
  }
543
587
 
@@ -576,6 +620,10 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
576
620
  //We need to set the current slab thickness here since setSlabThickness is define in VolumeViewport
577
621
  this.viewportProperties.slabThickness = slabThickness;
578
622
  }
623
+
624
+ if (rotation !== undefined) {
625
+ this.setRotation(rotation);
626
+ }
579
627
  }
580
628
 
581
629
  /**
@@ -609,6 +657,10 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
609
657
  this.viewportProperties.slabThickness = properties.slabThickness;
610
658
  }
611
659
 
660
+ if (properties.rotation !== undefined) {
661
+ this.setRotation(properties.rotation);
662
+ }
663
+
612
664
  this.render();
613
665
  }
614
666
 
@@ -683,6 +735,7 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
683
735
  interpolationType,
684
736
  invert,
685
737
  slabThickness,
738
+ rotation,
686
739
  } = this.viewportProperties;
687
740
 
688
741
  const voiRanges = this.getActors()
@@ -711,6 +764,7 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
711
764
  interpolationType: interpolationType,
712
765
  invert: invert,
713
766
  slabThickness: slabThickness,
767
+ rotation: rotation,
714
768
  };
715
769
  };
716
770
 
@@ -937,6 +991,49 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
937
991
  return true;
938
992
  }
939
993
 
994
+ /**
995
+ * Gets the rotation resulting from the value set in setRotation AND taking into
996
+ * account any flips that occurred subsequently from the camera provided or the viewport.
997
+ *
998
+ * @returns the rotation resulting from the value set in setRotation AND taking into
999
+ * account any flips that occurred subsequently.
1000
+ */
1001
+ public getRotation = (): number => {
1002
+ const {
1003
+ viewUp: currentViewUp,
1004
+ viewPlaneNormal,
1005
+ flipVertical,
1006
+ } = this.getCamera();
1007
+
1008
+ // The initial view up vector without any rotation, but incorporating vertical flip.
1009
+ const initialViewUp = flipVertical
1010
+ ? vec3.negate(vec3.create(), this.initialViewUp)
1011
+ : this.initialViewUp;
1012
+
1013
+ // The angle between the initial and current view up vectors.
1014
+ // TODO: check with VTK about rounding errors here.
1015
+ const initialToCurrentViewUpAngle =
1016
+ (vec3.angle(initialViewUp, currentViewUp) * 180) / Math.PI;
1017
+
1018
+ // Now determine if initialToCurrentViewUpAngle is positive or negative by comparing
1019
+ // the direction of the initial/current view up cross product with the current
1020
+ // viewPlaneNormal.
1021
+
1022
+ const initialToCurrentViewUpCross = vec3.cross(
1023
+ vec3.create(),
1024
+ initialViewUp,
1025
+ currentViewUp
1026
+ );
1027
+
1028
+ // The sign of the dot product of the start/end view up cross product and
1029
+ // the viewPlaneNormal indicates a positive or negative rotation respectively.
1030
+ const normalDot = vec3.dot(initialToCurrentViewUpCross, viewPlaneNormal);
1031
+
1032
+ return normalDot >= 0
1033
+ ? initialToCurrentViewUpAngle
1034
+ : (360 - initialToCurrentViewUpAngle) % 360;
1035
+ };
1036
+
940
1037
  /**
941
1038
  * gets the visible bounds of the viewport in the world coordinate system
942
1039
  */
@@ -1209,6 +1306,7 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
1209
1306
  typeof orientation === 'string' &&
1210
1307
  MPR_CAMERA_VALUES[orientation]
1211
1308
  ) {
1309
+ this.viewportProperties.orientation = orientation;
1212
1310
  return MPR_CAMERA_VALUES[orientation];
1213
1311
  } else {
1214
1312
  throw new Error(
@@ -18,7 +18,7 @@ import {
18
18
  isEqual,
19
19
  } from '../utilities';
20
20
  import hasNaNValues from '../utilities/hasNaNValues';
21
- import { EPSILON, RENDERING_DEFAULTS } from '../constants';
21
+ import { RENDERING_DEFAULTS } from '../constants';
22
22
  import type {
23
23
  ICamera,
24
24
  ActorEntry,
@@ -1190,7 +1190,11 @@ class Viewport implements IViewport {
1190
1190
  updatedCamera: ICamera
1191
1191
  ): Promise<void> {
1192
1192
  const actorEntries = this.getActors();
1193
- const allPromises = actorEntries.map(async (actorEntry) => {
1193
+ // Todo: this was using an async and promise wait all because of the
1194
+ // new surface rendering use case, which broke the more important 3D
1195
+ // volume rendering, so reverting this back for now until I can figure
1196
+ // out a better way to handle this.
1197
+ actorEntries.map((actorEntry) => {
1194
1198
  // we assume that the first two clipping plane of the mapper are always
1195
1199
  // the 'camera' clipping. Update clipping planes only if the actor is
1196
1200
  // a vtkVolume
@@ -1228,7 +1232,6 @@ class Viewport implements IViewport {
1228
1232
  });
1229
1233
  });
1230
1234
 
1231
- await Promise.all(allPromises);
1232
1235
  this.posProcessNewActors();
1233
1236
  }
1234
1237
 
@@ -42,7 +42,6 @@ class VolumeViewport extends BaseVolumeViewport {
42
42
  super(props);
43
43
 
44
44
  const { orientation } = this.options;
45
-
46
45
  // if the camera is set to be acquisition axis then we need to skip
47
46
  // it for now until the volume is set
48
47
  if (orientation && orientation !== OrientationAxis.ACQUISITION) {
@@ -144,6 +143,7 @@ class VolumeViewport extends BaseVolumeViewport {
144
143
  viewUp,
145
144
  });
146
145
 
146
+ this.viewportProperties.orientation = orientation;
147
147
  this.resetCamera();
148
148
 
149
149
  if (immediate) {
@@ -231,7 +231,8 @@ class VolumeViewport extends BaseVolumeViewport {
231
231
  public resetCamera(
232
232
  resetPan = true,
233
233
  resetZoom = true,
234
- resetToCenter = true
234
+ resetToCenter = true,
235
+ resetRotation = false
235
236
  ): boolean {
236
237
  super.resetCamera(resetPan, resetZoom, resetToCenter);
237
238
 
@@ -239,6 +240,7 @@ class VolumeViewport extends BaseVolumeViewport {
239
240
 
240
241
  const activeCamera = this.getVtkActiveCamera();
241
242
  const viewPlaneNormal = <Point3>activeCamera.getViewPlaneNormal();
243
+ const viewUp = <Point3>activeCamera.getViewUp();
242
244
  const focalPoint = <Point3>activeCamera.getFocalPoint();
243
245
 
244
246
  // always add clipping planes for the volume viewport. If a use case
@@ -274,6 +276,19 @@ class VolumeViewport extends BaseVolumeViewport {
274
276
  }
275
277
  });
276
278
 
279
+ //Only reset the rotation of the camera if wanted (so we don't reset everytime resetCamera is called) and also verify that the viewport has an orientation that we know (sagittal, coronal, axial)
280
+ if (
281
+ resetRotation &&
282
+ MPR_CAMERA_VALUES[this.viewportProperties.orientation] !== undefined
283
+ ) {
284
+ const viewToReset =
285
+ MPR_CAMERA_VALUES[this.viewportProperties.orientation];
286
+ this.setCameraNoEvent({
287
+ viewUp: viewToReset.viewUp,
288
+ viewPlaneNormal: viewToReset.viewPlaneNormal,
289
+ });
290
+ }
291
+
277
292
  return true;
278
293
  }
279
294
 
@@ -370,8 +385,6 @@ class VolumeViewport extends BaseVolumeViewport {
370
385
  return getClosestImageId(volume, focalPoint, viewPlaneNormal);
371
386
  };
372
387
 
373
- getRotation = (): number => 0;
374
-
375
388
  /**
376
389
  * Reset the viewport properties to the default values
377
390
  *
@@ -433,6 +446,12 @@ class VolumeViewport extends BaseVolumeViewport {
433
446
  volumeId: volumeActor.uid,
434
447
  };
435
448
 
449
+ const resetPan = true;
450
+ const resetZoom = true;
451
+ const resetToCenter = true;
452
+ const resetCameraRotation = true;
453
+ this.resetCamera(resetPan, resetZoom, resetToCenter, resetCameraRotation);
454
+
436
455
  triggerEvent(this.element, Events.VOI_MODIFIED, eventDetails);
437
456
  }
438
457
  }
@@ -42,7 +42,7 @@ interface IViewport {
42
42
  isDisabled: boolean;
43
43
  /** The rendering state of this viewport */
44
44
  viewportStatus: ViewportStatus;
45
- /** the rotation applied to the view */
45
+ /** get the rotation either from the camera provided or the viewport if not provided */
46
46
  getRotation: () => number;
47
47
  /** frameOfReferenceUID the viewport's default actor is rendering */
48
48
  getFrameOfReferenceUID: () => string;
@@ -136,7 +136,8 @@ export default interface IVolumeViewport extends IViewport {
136
136
  resetCamera(
137
137
  resetPan?: boolean,
138
138
  resetZoom?: boolean,
139
- resetToCenter?: boolean
139
+ resetToCenter?: boolean,
140
+ resetRotation?: boolean
140
141
  ): boolean;
141
142
  /**
142
143
  * Sets the blendMode for actors of the viewport.
@@ -16,6 +16,8 @@ type ViewportProperties = {
16
16
  colormap?: ColormapPublic;
17
17
  /** interpolation type */
18
18
  interpolationType?: InterpolationType;
19
+ /**Rotation of the camera */
20
+ rotation?: number;
19
21
  };
20
22
 
21
23
  export type { ViewportProperties };
@@ -1,4 +1,5 @@
1
1
  import { ViewportProperties } from './ViewportProperties';
2
+ import { OrientationAxis } from '../enums';
2
3
 
3
4
  /**
4
5
  * Stack Viewport Properties
@@ -8,6 +9,8 @@ type VolumeViewportProperties = ViewportProperties & {
8
9
  preset?: string;
9
10
 
10
11
  slabThickness?: number;
12
+
13
+ orientation?: OrientationAxis;
11
14
  };
12
15
 
13
16
  export default VolumeViewportProperties;
@@ -0,0 +1,166 @@
1
+ import { glMatrix, mat4, vec3 } from 'gl-matrix';
2
+ import { IVolumeViewport, Point3 } from '../types';
3
+ import { transformIJKToCanvas } from './transformIJKToCanvas';
4
+ import { transformCanvasToIJK } from './transformCanvasToIJK';
5
+
6
+ /**
7
+ * Get the image data for the current slice rendered on the viewport. The image
8
+ * data returned is the full slice and not only the region that is visible on
9
+ * the viewport. It does not work for oblique views.
10
+ * @param viewport - Volume viewport
11
+ * @returns Slice image dataand matrices to convert from volume
12
+ * to slice and vice-versa
13
+ */
14
+ function getCurrentVolumeViewportSlice(viewport: IVolumeViewport) {
15
+ const { dimensions, scalarData } = viewport.getImageData();
16
+ const { width: canvasWidth, height: canvasHeight } = viewport.getCanvas();
17
+
18
+ // Get three points from the canvas to help us identify the orientation of
19
+ // the slice. Using canvas width/height to get point far away for each other
20
+ // because points such as (0,0), (1,0) and (0,1) may be converted to the same
21
+ // ijk index when the image is zoomed in.
22
+ const ijkOriginPoint = transformCanvasToIJK(viewport, [0, 0]);
23
+ const ijkRowPoint = transformCanvasToIJK(viewport, [canvasWidth - 1, 0]);
24
+ const ijkColPoint = transformCanvasToIJK(viewport, [0, canvasHeight - 1]);
25
+
26
+ // Subtract the points to get the row and column vectors in index space
27
+ const ijkRowVec = vec3.sub(vec3.create(), ijkRowPoint, ijkOriginPoint);
28
+ const ijkColVec = vec3.sub(vec3.create(), ijkColPoint, ijkOriginPoint);
29
+ const ijkSliceVec = vec3.cross(vec3.create(), ijkRowVec, ijkColVec);
30
+
31
+ vec3.normalize(ijkRowVec, ijkRowVec);
32
+ vec3.normalize(ijkColVec, ijkColVec);
33
+ vec3.normalize(ijkSliceVec, ijkSliceVec);
34
+
35
+ // Any unit vector parallel to IJK have one component equal to 1 and
36
+ // the other two components equal to 0. If two of them are parallel
37
+ // the third one is also parallel
38
+ const maxIJKRowVec = Math.max(
39
+ Math.abs(ijkRowVec[0]),
40
+ Math.abs(ijkRowVec[1]),
41
+ Math.abs(ijkRowVec[2])
42
+ );
43
+ const maxIJKColVec = Math.max(
44
+ Math.abs(ijkColVec[0]),
45
+ Math.abs(ijkColVec[1]),
46
+ Math.abs(ijkColVec[2])
47
+ );
48
+
49
+ // Using glMatrix.equals() because the number may be not exactly equal to
50
+ // 1 due to rounding issues
51
+ if (!glMatrix.equals(1, maxIJKRowVec) || !glMatrix.equals(1, maxIJKColVec)) {
52
+ throw new Error('Livewire is not available for rotate/oblique viewports');
53
+ }
54
+
55
+ const [sx, sy, sz] = dimensions;
56
+
57
+ // All eight volume corners in index space
58
+ // prettier-ignore
59
+ const ijkCorners: Point3[] = [
60
+ [ 0, 0, 0], // top-left-front
61
+ [sx - 1, 0, 0], // top-right-front
62
+ [ 0, sy - 1, 0], // bottom-left-front
63
+ [sx - 1, sy - 1, 0], // bottom-right-front
64
+ [ 0, 0, sz - 1], // top-left-back
65
+ [sx - 1, 0, sz - 1], // top-right-back
66
+ [ 0, sy - 1, sz - 1], // bottom-left-back
67
+ [sx - 1, sy - 1, sz - 1], // bottom-right-back
68
+ ];
69
+
70
+ // Project the volume corners onto the canvas
71
+ const canvasCorners = ijkCorners.map((ijkCorner) =>
72
+ transformIJKToCanvas(viewport, ijkCorner)
73
+ );
74
+
75
+ // Calculate the AABB from the corners project onto the canvas
76
+ const canvasAABB = canvasCorners.reduce(
77
+ (aabb, canvasPoint) => {
78
+ aabb.minX = Math.min(aabb.minX, canvasPoint[0]);
79
+ aabb.minY = Math.min(aabb.minY, canvasPoint[1]);
80
+ aabb.maxX = Math.max(aabb.maxX, canvasPoint[0]);
81
+ aabb.maxY = Math.max(aabb.maxY, canvasPoint[1]);
82
+
83
+ return aabb;
84
+ },
85
+ { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity }
86
+ );
87
+
88
+ // Get the top-left, bottom-right and the diagonal vector of
89
+ // the slice in index space
90
+ const ijkTopLeft = transformCanvasToIJK(viewport, [
91
+ canvasAABB.minX,
92
+ canvasAABB.minY,
93
+ ]);
94
+ const ijkBottomRight = transformCanvasToIJK(viewport, [
95
+ canvasAABB.maxX,
96
+ canvasAABB.maxY,
97
+ ]);
98
+ const ijkDiagonal = vec3.sub(vec3.create(), ijkBottomRight, ijkTopLeft);
99
+
100
+ // prettier-ignore
101
+ const sliceToIndexMatrix = mat4.fromValues(
102
+ ijkRowVec[0], ijkRowVec[1], ijkRowVec[2], 0,
103
+ ijkColVec[0], ijkColVec[1], ijkColVec[2], 0,
104
+ ijkSliceVec[0], ijkSliceVec[1], ijkSliceVec[2], 0,
105
+ ijkTopLeft[0], ijkTopLeft[1], ijkTopLeft[2], 1
106
+ );
107
+
108
+ const indexToSliceMatrix = mat4.invert(mat4.create(), sliceToIndexMatrix);
109
+
110
+ // Dot the diagonal with row/column to find the image width/height
111
+ const sliceWidth = vec3.dot(ijkRowVec, ijkDiagonal) + 1;
112
+ const sliceHeight = vec3.dot(ijkColVec, ijkDiagonal) + 1;
113
+
114
+ // Create a TypedArray with same type from the original scalarData
115
+ const TypedArray = (scalarData as any).constructor;
116
+ const sliceData = new TypedArray(sliceWidth * sliceHeight);
117
+
118
+ // We need to know how many pixels to jump for every change in Z direction
119
+ const pixelsPerSlice = dimensions[0] * dimensions[1];
120
+
121
+ // Create two vectors to keep track of each row/column it is, reducing
122
+ // the amount of vec3 instances created and simplifying the math.
123
+ const ijkPixelRow = vec3.clone(ijkTopLeft);
124
+ const ijkPixelCol = vec3.create();
125
+
126
+ // Use an independent index to avoid multiple (x,y) to index conversions
127
+ let slicePixelIndex = 0;
128
+
129
+ for (let y = 0; y < sliceHeight; y++) {
130
+ vec3.copy(ijkPixelCol, ijkPixelRow);
131
+
132
+ for (let x = 0; x < sliceWidth; x++) {
133
+ const volumePixelIndex =
134
+ ijkPixelCol[2] * pixelsPerSlice +
135
+ ijkPixelCol[1] * dimensions[0] +
136
+ ijkPixelCol[0];
137
+
138
+ // It may never throw any "out of bounds" error but just to be safe
139
+ if (volumePixelIndex < scalarData.length) {
140
+ sliceData[slicePixelIndex] = scalarData[volumePixelIndex];
141
+ }
142
+
143
+ // Move to next slice pixel
144
+ slicePixelIndex++;
145
+
146
+ // Move to the next voxel
147
+ vec3.add(ijkPixelCol, ijkPixelCol, ijkRowVec);
148
+ }
149
+
150
+ // Move to the next row
151
+ vec3.add(ijkPixelRow, ijkPixelRow, ijkColVec);
152
+ }
153
+
154
+ return {
155
+ width: sliceWidth,
156
+ height: sliceHeight,
157
+ scalarData: sliceData,
158
+ sliceToIndexMatrix,
159
+ indexToSliceMatrix,
160
+ };
161
+ }
162
+
163
+ export {
164
+ getCurrentVolumeViewportSlice as default,
165
+ getCurrentVolumeViewportSlice,
166
+ };
@@ -26,6 +26,7 @@ import indexWithinDimensions from './indexWithinDimensions';
26
26
  import getVolumeViewportsContainingSameVolumes from './getVolumeViewportsContainingSameVolumes';
27
27
  import getViewportsWithVolumeId from './getViewportsWithVolumeId';
28
28
  import transformWorldToIndex from './transformWorldToIndex';
29
+ import transformIndexToWorld from './transformIndexToWorld';
29
30
  import loadImageToCanvas from './loadImageToCanvas';
30
31
  import renderToCanvasCPU from './renderToCanvasCPU';
31
32
  import renderToCanvasGPU from './renderToCanvasGPU';
@@ -39,6 +40,7 @@ import getImageSliceDataForVolumeViewport from './getImageSliceDataForVolumeView
39
40
  import { isImageActor, actorIsA } from './actorCheck';
40
41
  import getViewportsWithImageURI from './getViewportsWithImageURI';
41
42
  import getClosestStackImageIndexForPoint from './getClosestStackImageIndexForPoint';
43
+ import getCurrentVolumeViewportSlice from './getCurrentVolumeViewportSlice';
42
44
  import calculateViewportsSpatialRegistration from './calculateViewportsSpatialRegistration';
43
45
  import spatialRegistrationMetadataProvider from './spatialRegistrationMetadataProvider';
44
46
  import getViewportImageCornersInWorld from './getViewportImageCornersInWorld';
@@ -98,6 +100,7 @@ export {
98
100
  getVolumeViewportsContainingSameVolumes,
99
101
  getViewportsWithVolumeId,
100
102
  transformWorldToIndex,
103
+ transformIndexToWorld,
101
104
  loadImageToCanvas,
102
105
  renderToCanvasCPU,
103
106
  renderToCanvasGPU,
@@ -113,6 +116,7 @@ export {
113
116
  actorIsA,
114
117
  getViewportsWithImageURI,
115
118
  getClosestStackImageIndexForPoint,
119
+ getCurrentVolumeViewportSlice,
116
120
  calculateViewportsSpatialRegistration,
117
121
  spatialRegistrationMetadataProvider,
118
122
  getViewportImageCornersInWorld,
@@ -0,0 +1,18 @@
1
+ import { IStackViewport, IVolumeViewport, Point2 } from '../types';
2
+ import transformWorldToIndex from './transformWorldToIndex';
3
+
4
+ /**
5
+ * Convert coordinates from canvas to index (volume) space
6
+ * @param viewport - Stack or Volume viewport
7
+ * @param ijkPoint - 2D point in canvas space
8
+ * @returns 3D point in index (volume) space
9
+ */
10
+ export function transformCanvasToIJK(
11
+ viewport: IVolumeViewport | IStackViewport,
12
+ canvasPoint: Point2
13
+ ) {
14
+ const { imageData: vtkImageData } = viewport.getImageData();
15
+ const worldPoint = viewport.canvasToWorld(canvasPoint);
16
+
17
+ return transformWorldToIndex(vtkImageData, worldPoint);
18
+ }
@@ -0,0 +1,18 @@
1
+ import { IStackViewport, IVolumeViewport, Point3 } from '../types';
2
+ import transformIndexToWorld from './transformIndexToWorld';
3
+
4
+ /**
5
+ * Convert coordinates from index (volume) to canvas space
6
+ * @param viewport - Stack or Volume viewport
7
+ * @param ijkPoint - 3D point in index (volume) space
8
+ * @returns 2D point in canvas space
9
+ */
10
+ export function transformIJKToCanvas(
11
+ viewport: IVolumeViewport | IStackViewport,
12
+ ijkPoint: Point3
13
+ ) {
14
+ const { imageData: vtkImageData } = viewport.getImageData();
15
+ const worldPoint = transformIndexToWorld(vtkImageData, ijkPoint);
16
+
17
+ return viewport.worldToCanvas(worldPoint);
18
+ }
@@ -0,0 +1,14 @@
1
+ import type Point3 from '../types/Point3';
2
+
3
+ /**
4
+ * Given an imageData object and a position in voxel space, return a point
5
+ * in world space.
6
+ *
7
+ * @param imageData - The image data object.
8
+ * @param voxelPos - Point in voxel space
9
+ * index space.
10
+ * @returns A point in world space.
11
+ */
12
+ export default function transformIndexToWorld(imageData, voxelPos: Point3) {
13
+ return imageData.indexToWorld(voxelPos);
14
+ }