@cornerstonejs/core 0.41.1 → 0.42.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 (53) hide show
  1. package/dist/cjs/RenderingEngine/RenderingEngine.js +1 -0
  2. package/dist/cjs/RenderingEngine/RenderingEngine.js.map +1 -1
  3. package/dist/cjs/RenderingEngine/StackViewport.js +4 -2
  4. package/dist/cjs/RenderingEngine/StackViewport.js.map +1 -1
  5. package/dist/cjs/RenderingEngine/Viewport.d.ts +5 -1
  6. package/dist/cjs/RenderingEngine/Viewport.js +66 -0
  7. package/dist/cjs/RenderingEngine/Viewport.js.map +1 -1
  8. package/dist/cjs/enums/Events.d.ts +1 -0
  9. package/dist/cjs/enums/Events.js +1 -0
  10. package/dist/cjs/enums/Events.js.map +1 -1
  11. package/dist/cjs/types/EventTypes.d.ts +9 -1
  12. package/dist/cjs/types/IViewport.d.ts +3 -0
  13. package/dist/cjs/types/ViewportInputOptions.d.ts +2 -0
  14. package/dist/cjs/types/displayArea.d.ts +9 -0
  15. package/dist/cjs/types/displayArea.js +3 -0
  16. package/dist/cjs/types/displayArea.js.map +1 -0
  17. package/dist/cjs/types/index.d.ts +2 -1
  18. package/dist/cjs/utilities/isEqual.d.ts +1 -1
  19. package/dist/cjs/utilities/isEqual.js +30 -4
  20. package/dist/cjs/utilities/isEqual.js.map +1 -1
  21. package/dist/esm/RenderingEngine/RenderingEngine.js +1 -0
  22. package/dist/esm/RenderingEngine/RenderingEngine.js.map +1 -1
  23. package/dist/esm/RenderingEngine/StackViewport.js +4 -2
  24. package/dist/esm/RenderingEngine/StackViewport.js.map +1 -1
  25. package/dist/esm/RenderingEngine/Viewport.d.ts +5 -1
  26. package/dist/esm/RenderingEngine/Viewport.js +63 -0
  27. package/dist/esm/RenderingEngine/Viewport.js.map +1 -1
  28. package/dist/esm/enums/Events.d.ts +1 -0
  29. package/dist/esm/enums/Events.js +1 -0
  30. package/dist/esm/enums/Events.js.map +1 -1
  31. package/dist/esm/types/EventTypes.d.ts +9 -1
  32. package/dist/esm/types/IViewport.d.ts +3 -0
  33. package/dist/esm/types/ViewportInputOptions.d.ts +2 -0
  34. package/dist/esm/types/displayArea.d.ts +9 -0
  35. package/dist/esm/types/displayArea.js +2 -0
  36. package/dist/esm/types/displayArea.js.map +1 -0
  37. package/dist/esm/types/index.d.ts +2 -1
  38. package/dist/esm/utilities/isEqual.d.ts +1 -1
  39. package/dist/esm/utilities/isEqual.js +30 -4
  40. package/dist/esm/utilities/isEqual.js.map +1 -1
  41. package/dist/umd/index.js +1 -1
  42. package/dist/umd/index.js.map +1 -1
  43. package/package.json +2 -2
  44. package/src/RenderingEngine/RenderingEngine.ts +1 -0
  45. package/src/RenderingEngine/StackViewport.ts +6 -2
  46. package/src/RenderingEngine/Viewport.ts +105 -3
  47. package/src/enums/Events.ts +7 -0
  48. package/src/types/EventTypes.ts +23 -0
  49. package/src/types/IViewport.ts +9 -0
  50. package/src/types/ViewportInputOptions.ts +3 -1
  51. package/src/types/displayArea.ts +10 -0
  52. package/src/types/index.ts +2 -0
  53. package/src/utilities/isEqual.ts +60 -13
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cornerstonejs/core",
3
- "version": "0.41.1",
3
+ "version": "0.42.0",
4
4
  "description": "",
5
5
  "main": "dist/umd/index.js",
6
6
  "types": "dist/esm/index.d.ts",
@@ -51,5 +51,5 @@
51
51
  "type": "individual",
52
52
  "url": "https://ohif.org/donate"
53
53
  },
54
- "gitHead": "8e8c4693642dab1c46c38381f66730b40b187e79"
54
+ "gitHead": "1ddefd3cf71aff541732dd6879dd282866e37217"
55
55
  }
@@ -502,6 +502,7 @@ class RenderingEngine implements IRenderingEngine {
502
502
  options = {
503
503
  background: [0, 0, 0],
504
504
  orientation: null,
505
+ displayArea: null,
505
506
  };
506
507
 
507
508
  if (type === ViewportType.ORTHOGRAPHIC) {
@@ -1536,11 +1536,15 @@ class StackViewport extends Viewport implements IStackViewport {
1536
1536
  const columnCosines = direction.slice(3, 6);
1537
1537
  const dataType = imageData.getPointData().getScalars().getDataType();
1538
1538
 
1539
+ // using epsilon comparison for float numbers comparison.
1540
+ const isSameXSpacing = isEqual(xSpacing, image.rowPixelSpacing);
1541
+ const isSameYSpacing = isEqual(ySpacing, image.columnPixelSpacing);
1542
+
1539
1543
  // using spacing, size, and direction only for now
1540
1544
  return (
1541
- (xSpacing === image.rowPixelSpacing ||
1545
+ (isSameXSpacing ||
1542
1546
  (image.rowPixelSpacing === null && xSpacing === 1.0)) &&
1543
- (ySpacing === image.columnPixelSpacing ||
1547
+ (isSameYSpacing ||
1544
1548
  (image.columnPixelSpacing === null && ySpacing === 1.0)) &&
1545
1549
  xVoxels === image.columns &&
1546
1550
  yVoxels === image.rows &&
@@ -21,6 +21,7 @@ import type {
21
21
  Point3,
22
22
  FlipDirection,
23
23
  EventTypes,
24
+ DisplayArea,
24
25
  } from '../types';
25
26
  import type { ViewportInput, IViewport } from '../types/IViewport';
26
27
  import type { vtkSlabCamera } from './vtkClasses/vtkSlabCamera';
@@ -62,7 +63,7 @@ class Viewport implements IViewport {
62
63
  _actors: Map<string, any>;
63
64
  /** Default options for the viewport which includes orientation, viewPlaneNormal and backgroundColor */
64
65
  readonly defaultOptions: any;
65
- /** options for the viewport which includes orientation axis and backgroundColor */
66
+ /** options for the viewport which includes orientation axis, backgroundColor and displayArea */
66
67
  options: ViewportInputOptions;
67
68
  private _suppressCameraModifiedEvents = false;
68
69
  /** A flag representing if viewport methods should fire events or not */
@@ -72,6 +73,10 @@ class Viewport implements IViewport {
72
73
  * the relative pan/zoom
73
74
  */
74
75
  protected initialCamera: ICamera;
76
+ /** The camera that is defined for resetting displayArea to ensure absolute displayArea
77
+ * settings
78
+ */
79
+ private fitToCanvasCamera: ICamera;
75
80
 
76
81
  constructor(props: ViewportInput) {
77
82
  this.id = props.id;
@@ -155,7 +160,9 @@ class Viewport implements IViewport {
155
160
 
156
161
  // TODO When this is needed we need to move the camera position.
157
162
  // We can steal some logic from the tools we build to do this.
158
-
163
+ if (this.options?.displayArea) {
164
+ this.setDisplayArea(this.options?.displayArea);
165
+ }
159
166
  if (immediate) {
160
167
  this.render();
161
168
  }
@@ -528,6 +535,80 @@ class Viewport implements IViewport {
528
535
  return intersections;
529
536
  }
530
537
 
538
+ /**
539
+ * Sets the camera to an initial bounds. If
540
+ * resetPan and resetZoom are true it places the focal point at the center of
541
+ * the volume (or slice); otherwise, only the camera zoom and camera Pan or Zoom
542
+ * is reset for the current view.
543
+ * @param displayArea - The display area of interest.
544
+ * @param suppressEvents - If true, don't fire displayArea event.
545
+ */
546
+ public setDisplayArea(
547
+ displayArea: DisplayArea,
548
+ suppressEvents = false
549
+ ): void {
550
+ const { storeAsInitialCamera } = displayArea;
551
+
552
+ // make calculations relative to the fitToCanvasCamera view
553
+ this.setCamera(this.fitToCanvasCamera, false);
554
+
555
+ const { imageArea, imageCanvasPoint } = displayArea;
556
+
557
+ if (imageArea) {
558
+ const [areaX, areaY] = imageArea;
559
+ const zoom = Math.min(this.getZoom() / areaX, this.getZoom() / areaY);
560
+ this.setZoom(zoom, storeAsInitialCamera);
561
+ }
562
+
563
+ // getting the image info
564
+ const imageData = this.getDefaultImageData();
565
+ if (imageCanvasPoint && imageData) {
566
+ const { imagePoint, canvasPoint } = imageCanvasPoint;
567
+ const [canvasX, canvasY] = canvasPoint;
568
+ const devicePixelRatio = window?.devicePixelRatio || 1;
569
+ const validateCanvasPanX = this.sWidth / devicePixelRatio;
570
+ const validateCanvasPanY = this.sHeight / devicePixelRatio;
571
+ const canvasPanX = validateCanvasPanX * (canvasX - 0.5);
572
+ const canvasPanY = validateCanvasPanY * (canvasY - 0.5);
573
+
574
+ const dimensions = imageData.getDimensions();
575
+ const canvasZero = this.worldToCanvas([0, 0, 0]);
576
+ const canvasEdge = this.worldToCanvas(dimensions);
577
+ const canvasImage = [
578
+ canvasEdge[0] - canvasZero[0],
579
+ canvasEdge[1] - canvasZero[1],
580
+ ];
581
+ const [imgWidth, imgHeight] = canvasImage;
582
+ const [imageX, imageY] = imagePoint;
583
+ const imagePanX = imgWidth * (0.5 - imageX);
584
+ const imagePanY = imgHeight * (0.5 - imageY);
585
+
586
+ const newPositionX = imagePanX + canvasPanX;
587
+ const newPositionY = imagePanY + canvasPanY;
588
+
589
+ const deltaPoint2: Point2 = [newPositionX, newPositionY];
590
+ this.setPan(deltaPoint2, storeAsInitialCamera);
591
+ }
592
+
593
+ if (storeAsInitialCamera) {
594
+ this.options.displayArea = displayArea;
595
+ }
596
+
597
+ if (!suppressEvents) {
598
+ const eventDetail: EventTypes.DisplayAreaModifiedEventDetail = {
599
+ viewportId: this.id,
600
+ displayArea: displayArea,
601
+ storeAsInitialCamera: storeAsInitialCamera,
602
+ };
603
+
604
+ triggerEvent(this.element, Events.DISPLAY_AREA_MODIFIED, eventDetail);
605
+ }
606
+ }
607
+
608
+ public getDisplayArea(): DisplayArea | undefined {
609
+ return this.options?.displayArea;
610
+ }
611
+
531
612
  /**
532
613
  * Resets the camera based on the rendering volume(s) bounds. If
533
614
  * resetPan and resetZoom are true it places the focal point at the center of
@@ -556,7 +637,6 @@ class Viewport implements IViewport {
556
637
  });
557
638
 
558
639
  const previousCamera = _cloneDeep(this.getCamera());
559
-
560
640
  const bounds = renderer.computeVisiblePropBounds();
561
641
  const focalPoint = <Point3>[0, 0, 0];
562
642
  const imageData = this.getDefaultImageData();
@@ -673,6 +753,8 @@ class Viewport implements IViewport {
673
753
 
674
754
  const modifiedCamera = _cloneDeep(this.getCamera());
675
755
 
756
+ this.setFitToCanvasCamera(_cloneDeep(this.getCamera()));
757
+
676
758
  if (storeAsInitialCamera) {
677
759
  this.setInitialCamera(modifiedCamera);
678
760
  }
@@ -688,6 +770,16 @@ class Viewport implements IViewport {
688
770
 
689
771
  this.triggerCameraModifiedEventIfNecessary(previousCamera, modifiedCamera);
690
772
 
773
+ if (
774
+ imageData &&
775
+ this.options?.displayArea &&
776
+ resetZoom &&
777
+ resetPan &&
778
+ resetToCenter
779
+ ) {
780
+ this.setDisplayArea(this.options?.displayArea);
781
+ }
782
+
691
783
  return true;
692
784
  }
693
785
 
@@ -701,6 +793,16 @@ class Viewport implements IViewport {
701
793
  this.initialCamera = camera;
702
794
  }
703
795
 
796
+ /**
797
+ * Sets the provided camera as the displayArea camera.
798
+ * This allows computing differences applied later as compared to the initial
799
+ * position, for things like zoom and pan.
800
+ * @param camera - to store as the initial value.
801
+ */
802
+ protected setFitToCanvasCamera(camera: ICamera): void {
803
+ this.fitToCanvasCamera = camera;
804
+ }
805
+
704
806
  /**
705
807
  * Helper function to return the current canvas pan value.
706
808
  *
@@ -38,6 +38,13 @@ enum Events {
38
38
  * and see what event detail is included in {@link EventTypes.VoiModifiedEventDetail | VoiModified Event Detail }
39
39
  */
40
40
  VOI_MODIFIED = 'CORNERSTONE_VOI_MODIFIED',
41
+ /**
42
+ * Triggers on the HTML element when viewport modifies its display area
43
+ *
44
+ * Make use of {@link EventTypes.DisplayAreaModifiedEvent | DisplayAreaModified Event Type } for typing your event listeners for DISPLAY_AREA_MODIFIED event,
45
+ * and see what event detail is included in {@link EventTypes.DisplayAreaModifiedEventDetail | DisplayAreaModified Event Detail }
46
+ */
47
+ DISPLAY_AREA_MODIFIED = 'CORNERSTONE_DISPLAY_AREA_MODIFIED',
41
48
  /**
42
49
  * Triggers on the eventTarget when the element is disabled
43
50
  *
@@ -8,6 +8,8 @@ import type IImage from './IImage';
8
8
  import type IImageVolume from './IImageVolume';
9
9
  import type { VOIRange } from './voi';
10
10
  import type VOILUTFunctionType from '../enums/VOILUTFunctionType';
11
+ import type DisplayArea from './displayArea';
12
+
11
13
  /**
12
14
  * CAMERA_MODIFIED Event's data
13
15
  */
@@ -40,6 +42,20 @@ type VoiModifiedEventDetail = {
40
42
  VOILUTFunction?: VOILUTFunctionType;
41
43
  };
42
44
 
45
+ /**
46
+ * DISPLAY_AREA_MODIFIED Event's data
47
+ */
48
+ type DisplayAreaModifiedEventDetail = {
49
+ /** Viewport Unique ID in the renderingEngine */
50
+ viewportId: string;
51
+ /** new display area */
52
+ displayArea: DisplayArea;
53
+ /** Unique ID for the volume in the cache */
54
+ volumeId?: string;
55
+ /** Whether displayArea was stored as initial view */
56
+ storeAsInitialCamera?: boolean;
57
+ };
58
+
43
59
  /**
44
60
  * ELEMENT_DISABLED Event's data
45
61
  */
@@ -259,6 +275,11 @@ type CameraModifiedEvent = CustomEventType<CameraModifiedEventDetail>;
259
275
  */
260
276
  type VoiModifiedEvent = CustomEventType<VoiModifiedEventDetail>;
261
277
 
278
+ /**
279
+ * DISPLAY_AREA_MODIFIED Event type
280
+ */
281
+ type DisplayAreaModifiedEvent = CustomEventType<DisplayAreaModifiedEventDetail>;
282
+
262
283
  /**
263
284
  * ELEMENT_DISABLED Event type
264
285
  */
@@ -362,6 +383,8 @@ export type {
362
383
  CameraModifiedEvent,
363
384
  VoiModifiedEvent,
364
385
  VoiModifiedEventDetail,
386
+ DisplayAreaModifiedEvent,
387
+ DisplayAreaModifiedEventDetail,
365
388
  ElementDisabledEvent,
366
389
  ElementDisabledEventDetail,
367
390
  ElementEnabledEvent,
@@ -4,6 +4,7 @@ import Point3 from './Point3';
4
4
  import ViewportInputOptions from './ViewportInputOptions';
5
5
  import { ActorEntry } from './IActor';
6
6
  import ViewportType from '../enums/ViewportType';
7
+ import DisplayArea from './displayArea';
7
8
 
8
9
  /**
9
10
  * Viewport interface for cornerstone viewports
@@ -73,6 +74,14 @@ interface IViewport {
73
74
  render(): void;
74
75
  /** set options for the viewport */
75
76
  setOptions(options: ViewportInputOptions, immediate: boolean): void;
77
+ /** set displayArea for the viewport */
78
+ setDisplayArea(
79
+ displayArea: DisplayArea,
80
+ callResetCamera?: boolean,
81
+ suppressEvents?: boolean
82
+ );
83
+ /** returns the displayArea */
84
+ getDisplayArea(): DisplayArea | undefined;
76
85
  /** reset camera and options*/
77
86
  reset(immediate: boolean): void;
78
87
  /** returns the canvas */
@@ -1,6 +1,6 @@
1
1
  import { OrientationAxis } from '../enums';
2
2
  import OrientationVectors from './OrientationVectors';
3
-
3
+ import DisplayArea from './displayArea';
4
4
  /**
5
5
  * This type defines the shape of viewport input options, so we can throw when it is incorrect.
6
6
  */
@@ -9,6 +9,8 @@ type ViewportInputOptions = {
9
9
  background?: [number, number, number];
10
10
  /** orientation of the viewport which can be either an Enum for axis Enums.OrientationAxis.[AXIAL|SAGITTAL|CORONAL|DEFAULT] or an object with viewPlaneNormal and viewUp */
11
11
  orientation?: OrientationAxis | OrientationVectors;
12
+ /** displayArea of interest */
13
+ displayArea?: DisplayArea;
12
14
  /** whether the events should be suppressed and not fired*/
13
15
  suppressEvents?: boolean;
14
16
  /**
@@ -0,0 +1,10 @@
1
+ type DisplayArea = {
2
+ imageArea: [number, number]; // areaX, areaY
3
+ imageCanvasPoint: {
4
+ imagePoint: [number, number]; // imageX, imageY
5
+ canvasPoint: [number, number]; // canvasX, canvasY
6
+ };
7
+ storeAsInitialCamera: boolean;
8
+ };
9
+
10
+ export default DisplayArea;
@@ -5,6 +5,7 @@ import type IEnabledElement from './IEnabledElement';
5
5
  import type ICache from './ICache';
6
6
  import type { IVolume, VolumeScalarData } from './IVolume';
7
7
  import type { VOI, VOIRange } from './voi';
8
+ import type DisplayArea from './displayArea';
8
9
  import type ImageLoaderFn from './ImageLoaderFn';
9
10
  import type IImageVolume from './IImageVolume';
10
11
  import type IDynamicImageVolume from './IDynamicImageVolume';
@@ -125,6 +126,7 @@ export type {
125
126
  ViewportInputOptions,
126
127
  VOIRange,
127
128
  VOI,
129
+ DisplayArea,
128
130
  FlipDirection,
129
131
  ICachedImage,
130
132
  ICachedVolume,
@@ -1,27 +1,74 @@
1
+ function areNumbersEqualWithTolerance(
2
+ num1: number,
3
+ num2: number,
4
+ tolerance: number
5
+ ): boolean {
6
+ return Math.abs(num1 - num2) <= tolerance;
7
+ }
8
+
9
+ function areArraysEqual(
10
+ arr1: ArrayLike<number>,
11
+ arr2: ArrayLike<number>,
12
+ tolerance = 1e-5
13
+ ): boolean {
14
+ if (arr1.length !== arr2.length) {
15
+ return false;
16
+ }
17
+
18
+ for (let i = 0; i < arr1.length; i++) {
19
+ if (!areNumbersEqualWithTolerance(arr1[i], arr2[i], tolerance)) {
20
+ return false;
21
+ }
22
+ }
23
+
24
+ return true;
25
+ }
26
+
27
+ function isNumberType(value: any): value is number {
28
+ return typeof value === 'number';
29
+ }
30
+
31
+ function isNumberArrayLike(value: any): value is ArrayLike<number> {
32
+ return 'length' in value && typeof value[0] === 'number';
33
+ }
34
+
1
35
  /**
2
- * returns equal if the two arrays are identical within the
3
- * given tolerance.
36
+ * Returns whether two values are equal or not, based on epsilon comparison.
37
+ * For array comparison, it does NOT strictly compare them but only compare its values.
38
+ * It can compare array of numbers and also typed array. Otherwise it will just return false.
4
39
  *
5
- * @param v1 - The first array of values
6
- * @param v2 - The second array of values.
40
+ * @param v1 - The first value to compare
41
+ * @param v2 - The second value to compare
7
42
  * @param tolerance - The acceptable tolerance, the default is 0.00001
8
43
  *
9
44
  * @returns True if the two values are within the tolerance levels.
10
45
  */
11
- export default function isEqual(
12
- v1: number[] | Float32Array,
13
- v2: number[] | Float32Array,
46
+ export default function isEqual<ValueType>(
47
+ v1: ValueType,
48
+ v2: ValueType,
14
49
  tolerance = 1e-5
15
50
  ): boolean {
16
- if (v1.length !== v2.length) {
51
+ // values must be the same type or not null
52
+ if (typeof v1 !== typeof v2 || v1 === null || v2 === null) {
17
53
  return false;
18
54
  }
19
55
 
20
- for (let i = 0; i < v1.length; i++) {
21
- if (Math.abs(v1[i] - v2[i]) > tolerance) {
22
- return false;
23
- }
56
+ // typeof object must have same constructor
57
+ if (
58
+ typeof v1 === 'object' &&
59
+ typeof v2 === 'object' &&
60
+ v1.constructor !== v2.constructor
61
+ ) {
62
+ return false;
24
63
  }
25
64
 
26
- return true;
65
+ if (isNumberType(v1) && isNumberType(v2)) {
66
+ return areNumbersEqualWithTolerance(v1, v2, tolerance);
67
+ }
68
+
69
+ if (isNumberArrayLike(v1) && isNumberArrayLike(v2)) {
70
+ return areArraysEqual(v1, v2, tolerance);
71
+ }
72
+
73
+ return false;
27
74
  }