@cornerstonejs/core 1.69.0 → 1.70.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.
- package/dist/cjs/RenderingEngine/BaseVolumeViewport.d.ts +3 -3
- package/dist/cjs/RenderingEngine/BaseVolumeViewport.js +36 -17
- package/dist/cjs/RenderingEngine/BaseVolumeViewport.js.map +1 -1
- package/dist/cjs/RenderingEngine/CanvasActor/index.js +1 -1
- package/dist/cjs/RenderingEngine/CanvasActor/index.js.map +1 -1
- package/dist/cjs/RenderingEngine/RenderingEngine.js +8 -26
- package/dist/cjs/RenderingEngine/RenderingEngine.js.map +1 -1
- package/dist/cjs/RenderingEngine/StackViewport.d.ts +5 -4
- package/dist/cjs/RenderingEngine/StackViewport.js +46 -24
- package/dist/cjs/RenderingEngine/StackViewport.js.map +1 -1
- package/dist/cjs/RenderingEngine/VideoViewport.d.ts +2 -2
- package/dist/cjs/RenderingEngine/VideoViewport.js +5 -9
- package/dist/cjs/RenderingEngine/VideoViewport.js.map +1 -1
- package/dist/cjs/RenderingEngine/Viewport.d.ts +17 -7
- package/dist/cjs/RenderingEngine/Viewport.js +173 -64
- package/dist/cjs/RenderingEngine/Viewport.js.map +1 -1
- package/dist/cjs/RenderingEngine/helpers/getOrCreateCanvas.d.ts +1 -0
- package/dist/cjs/RenderingEngine/helpers/getOrCreateCanvas.js +15 -2
- package/dist/cjs/RenderingEngine/helpers/getOrCreateCanvas.js.map +1 -1
- package/dist/cjs/cache/cache.js +0 -1
- package/dist/cjs/cache/cache.js.map +1 -1
- package/dist/cjs/loaders/volumeLoader.js +1 -1
- package/dist/cjs/loaders/volumeLoader.js.map +1 -1
- package/dist/cjs/types/IViewport.d.ts +18 -0
- package/dist/cjs/types/ViewportProperties.d.ts +1 -2
- package/dist/cjs/types/displayArea.d.ts +5 -1
- package/dist/cjs/types/index.d.ts +2 -2
- package/dist/cjs/utilities/index.d.ts +2 -1
- package/dist/cjs/utilities/index.js +3 -1
- package/dist/cjs/utilities/index.js.map +1 -1
- package/dist/cjs/utilities/loadImageToCanvas.d.ts +6 -2
- package/dist/cjs/utilities/loadImageToCanvas.js +14 -2
- package/dist/cjs/utilities/loadImageToCanvas.js.map +1 -1
- package/dist/cjs/utilities/renderToCanvasCPU.d.ts +2 -2
- package/dist/cjs/utilities/renderToCanvasCPU.js +1 -1
- package/dist/cjs/utilities/renderToCanvasCPU.js.map +1 -1
- package/dist/cjs/utilities/renderToCanvasGPU.d.ts +2 -2
- package/dist/cjs/utilities/renderToCanvasGPU.js +31 -12
- package/dist/cjs/utilities/renderToCanvasGPU.js.map +1 -1
- package/dist/esm/RenderingEngine/BaseVolumeViewport.js +37 -18
- package/dist/esm/RenderingEngine/BaseVolumeViewport.js.map +1 -1
- package/dist/esm/RenderingEngine/CanvasActor/index.js +1 -1
- package/dist/esm/RenderingEngine/CanvasActor/index.js.map +1 -1
- package/dist/esm/RenderingEngine/RenderingEngine.js +8 -26
- package/dist/esm/RenderingEngine/RenderingEngine.js.map +1 -1
- package/dist/esm/RenderingEngine/StackViewport.js +47 -25
- package/dist/esm/RenderingEngine/StackViewport.js.map +1 -1
- package/dist/esm/RenderingEngine/VideoViewport.js +5 -9
- package/dist/esm/RenderingEngine/VideoViewport.js.map +1 -1
- package/dist/esm/RenderingEngine/Viewport.js +173 -64
- package/dist/esm/RenderingEngine/Viewport.js.map +1 -1
- package/dist/esm/RenderingEngine/helpers/getOrCreateCanvas.js +14 -1
- package/dist/esm/RenderingEngine/helpers/getOrCreateCanvas.js.map +1 -1
- package/dist/esm/cache/cache.js +0 -1
- package/dist/esm/cache/cache.js.map +1 -1
- package/dist/esm/loaders/volumeLoader.js +1 -1
- package/dist/esm/loaders/volumeLoader.js.map +1 -1
- package/dist/esm/utilities/index.js +2 -1
- package/dist/esm/utilities/index.js.map +1 -1
- package/dist/esm/utilities/loadImageToCanvas.js +14 -2
- package/dist/esm/utilities/loadImageToCanvas.js.map +1 -1
- package/dist/esm/utilities/renderToCanvasCPU.js +1 -1
- package/dist/esm/utilities/renderToCanvasCPU.js.map +1 -1
- package/dist/esm/utilities/renderToCanvasGPU.js +8 -9
- package/dist/esm/utilities/renderToCanvasGPU.js.map +1 -1
- package/dist/types/RenderingEngine/BaseVolumeViewport.d.ts +3 -3
- package/dist/types/RenderingEngine/BaseVolumeViewport.d.ts.map +1 -1
- package/dist/types/RenderingEngine/CanvasActor/index.d.ts.map +1 -1
- package/dist/types/RenderingEngine/RenderingEngine.d.ts.map +1 -1
- package/dist/types/RenderingEngine/StackViewport.d.ts +5 -4
- package/dist/types/RenderingEngine/StackViewport.d.ts.map +1 -1
- package/dist/types/RenderingEngine/VideoViewport.d.ts +2 -2
- package/dist/types/RenderingEngine/VideoViewport.d.ts.map +1 -1
- package/dist/types/RenderingEngine/Viewport.d.ts +17 -7
- package/dist/types/RenderingEngine/Viewport.d.ts.map +1 -1
- package/dist/types/RenderingEngine/helpers/getOrCreateCanvas.d.ts +1 -0
- package/dist/types/RenderingEngine/helpers/getOrCreateCanvas.d.ts.map +1 -1
- package/dist/types/cache/cache.d.ts.map +1 -1
- package/dist/types/loaders/volumeLoader.d.ts.map +1 -1
- package/dist/types/types/IViewport.d.ts +18 -0
- package/dist/types/types/IViewport.d.ts.map +1 -1
- package/dist/types/types/ViewportProperties.d.ts +1 -2
- package/dist/types/types/ViewportProperties.d.ts.map +1 -1
- package/dist/types/types/displayArea.d.ts +5 -1
- package/dist/types/types/displayArea.d.ts.map +1 -1
- package/dist/types/types/index.d.ts +2 -2
- package/dist/types/types/index.d.ts.map +1 -1
- package/dist/types/utilities/index.d.ts +2 -1
- package/dist/types/utilities/index.d.ts.map +1 -1
- package/dist/types/utilities/loadImageToCanvas.d.ts +6 -2
- package/dist/types/utilities/loadImageToCanvas.d.ts.map +1 -1
- package/dist/types/utilities/renderToCanvasCPU.d.ts +2 -2
- package/dist/types/utilities/renderToCanvasCPU.d.ts.map +1 -1
- package/dist/types/utilities/renderToCanvasGPU.d.ts +2 -2
- package/dist/types/utilities/renderToCanvasGPU.d.ts.map +1 -1
- package/dist/umd/index.js +1 -1
- package/dist/umd/index.js.map +1 -1
- package/package.json +2 -2
- package/src/RenderingEngine/BaseVolumeViewport.ts +30 -6
- package/src/RenderingEngine/CanvasActor/index.ts +2 -1
- package/src/RenderingEngine/RenderingEngine.ts +11 -35
- package/src/RenderingEngine/StackViewport.ts +55 -14
- package/src/RenderingEngine/VideoViewport.ts +14 -9
- package/src/RenderingEngine/Viewport.ts +353 -91
- package/src/RenderingEngine/helpers/getOrCreateCanvas.ts +32 -1
- package/src/cache/cache.ts +2 -1
- package/src/loaders/volumeLoader.ts +2 -1
- package/src/types/IViewport.ts +130 -10
- package/src/types/ViewportProperties.ts +1 -3
- package/src/types/displayArea.ts +6 -1
- package/src/types/index.ts +4 -0
- package/src/utilities/index.ts +2 -0
- package/src/utilities/loadImageToCanvas.ts +38 -3
- package/src/utilities/renderToCanvasCPU.ts +7 -2
- package/src/utilities/renderToCanvasGPU.ts +20 -14
|
@@ -29,17 +29,21 @@ import type {
|
|
|
29
29
|
FlipDirection,
|
|
30
30
|
EventTypes,
|
|
31
31
|
DisplayArea,
|
|
32
|
+
ViewPresentation,
|
|
33
|
+
ViewReference,
|
|
34
|
+
ViewportProperties,
|
|
32
35
|
} from '../types';
|
|
33
36
|
import type {
|
|
34
37
|
ViewportInput,
|
|
35
38
|
IViewport,
|
|
36
39
|
ViewReferenceSpecifier,
|
|
37
|
-
ViewReference,
|
|
38
40
|
ReferenceCompatibleOptions,
|
|
41
|
+
ViewPresentationSelector,
|
|
39
42
|
} from '../types/IViewport';
|
|
40
43
|
import type { vtkSlabCamera } from './vtkClasses/vtkSlabCamera';
|
|
41
44
|
import { getConfiguration } from '../init';
|
|
42
45
|
import IImageCalibration from '../types/IImageCalibration';
|
|
46
|
+
import { InterpolationType } from '../enums';
|
|
43
47
|
|
|
44
48
|
/**
|
|
45
49
|
* An object representing a single viewport, which is a camera
|
|
@@ -51,6 +55,31 @@ import IImageCalibration from '../types/IImageCalibration';
|
|
|
51
55
|
* logic.
|
|
52
56
|
*/
|
|
53
57
|
class Viewport implements IViewport {
|
|
58
|
+
/**
|
|
59
|
+
* CameraViewPresentation is a view preentation selector that has all the
|
|
60
|
+
* camera related presentation selections, and would typically be used for
|
|
61
|
+
* choosing presentation information between two viewports showing the same
|
|
62
|
+
* type of orientation of a view, such as the CT, PT and fusion views in the
|
|
63
|
+
* same orientation view.
|
|
64
|
+
*/
|
|
65
|
+
public static readonly CameraViewPresentation: ViewPresentationSelector = {
|
|
66
|
+
rotation: true,
|
|
67
|
+
pan: true,
|
|
68
|
+
zoom: true,
|
|
69
|
+
displayArea: true,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* TransferViewPresentation is a view presentation selector that selects all
|
|
74
|
+
* the transfer function related attributes. It would typically be used for
|
|
75
|
+
* synchronizing different orientations of the same series, or for
|
|
76
|
+
* synchronizing two views of the same type of series such as a CT.
|
|
77
|
+
*/
|
|
78
|
+
public static readonly TransferViewPresentation: ViewPresentationSelector = {
|
|
79
|
+
windowLevel: true,
|
|
80
|
+
paletteLut: true,
|
|
81
|
+
};
|
|
82
|
+
|
|
54
83
|
/** unique identifier for the viewport */
|
|
55
84
|
readonly id: string;
|
|
56
85
|
/** HTML element in DOM that is used for rendering the viewport */
|
|
@@ -89,7 +118,7 @@ class Viewport implements IViewport {
|
|
|
89
118
|
/** options for the viewport which includes orientation axis, backgroundColor and displayArea */
|
|
90
119
|
options: ViewportInputOptions;
|
|
91
120
|
/** informs if a new actor was added before a resetCameraClippingRange phase */
|
|
92
|
-
|
|
121
|
+
_suppressCameraModifiedEvents = false;
|
|
93
122
|
/** A flag representing if viewport methods should fire events or not */
|
|
94
123
|
readonly suppressEvents: boolean;
|
|
95
124
|
protected hasPixelSpacing = true;
|
|
@@ -101,7 +130,7 @@ class Viewport implements IViewport {
|
|
|
101
130
|
/** The camera that is defined for resetting displayArea to ensure absolute displayArea
|
|
102
131
|
* settings
|
|
103
132
|
*/
|
|
104
|
-
|
|
133
|
+
protected fitToCanvasCamera: ICamera;
|
|
105
134
|
|
|
106
135
|
constructor(props: ViewportInput) {
|
|
107
136
|
this.id = props.id;
|
|
@@ -135,9 +164,12 @@ class Viewport implements IViewport {
|
|
|
135
164
|
worldToCanvas: (worldPos: Point3) => Point2;
|
|
136
165
|
customRenderViewportToCanvas: () => unknown;
|
|
137
166
|
resize: () => void;
|
|
138
|
-
getProperties: () =>
|
|
167
|
+
getProperties: () => ViewportProperties = () => ({});
|
|
139
168
|
updateRenderingPipeline: () => void;
|
|
140
169
|
getNumberOfSlices: () => number;
|
|
170
|
+
protected setRotation = (_rotation: number) => {
|
|
171
|
+
/*empty*/
|
|
172
|
+
};
|
|
141
173
|
|
|
142
174
|
static get useCustomRenderingPipeline(): boolean {
|
|
143
175
|
return false;
|
|
@@ -590,6 +622,13 @@ class Viewport implements IViewport {
|
|
|
590
622
|
return intersections;
|
|
591
623
|
}
|
|
592
624
|
|
|
625
|
+
/**
|
|
626
|
+
* Sets the interpolation type. No-op in the base.
|
|
627
|
+
*/
|
|
628
|
+
protected setInterpolationType(_interpolationType: InterpolationType, _arg?) {
|
|
629
|
+
// No-op - just done to allow setting on the base viewport
|
|
630
|
+
}
|
|
631
|
+
|
|
593
632
|
/**
|
|
594
633
|
* Sets the camera to an initial bounds. If
|
|
595
634
|
* resetPan and resetZoom are true it places the focal point at the center of
|
|
@@ -602,70 +641,43 @@ class Viewport implements IViewport {
|
|
|
602
641
|
displayArea: DisplayArea,
|
|
603
642
|
suppressEvents = false
|
|
604
643
|
): void {
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
// events because the camera is still changing.
|
|
610
|
-
this.setCameraNoEvent(this.fitToCanvasCamera);
|
|
611
|
-
|
|
612
|
-
const { imageArea, imageCanvasPoint } = displayArea;
|
|
644
|
+
if (!displayArea) {
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
const { storeAsInitialCamera, type: areaType } = displayArea;
|
|
613
648
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
// Don't set as initial camera because then the zoom interactions don't
|
|
619
|
-
// work consistently.
|
|
620
|
-
// TODO: Add a better method to handle initial camera
|
|
621
|
-
this.setZoom(this.insetImageMultiplier * zoom);
|
|
649
|
+
// Instead of storing the camera itself, if initial camera is set,
|
|
650
|
+
// then store the display area as the baseline display area.
|
|
651
|
+
if (storeAsInitialCamera) {
|
|
652
|
+
this.options.displayArea = displayArea;
|
|
622
653
|
}
|
|
623
654
|
|
|
624
|
-
//
|
|
625
|
-
const
|
|
626
|
-
|
|
627
|
-
const { imagePoint, canvasPoint } = imageCanvasPoint;
|
|
628
|
-
const [canvasX, canvasY] = canvasPoint;
|
|
629
|
-
const devicePixelRatio = window?.devicePixelRatio || 1;
|
|
630
|
-
const validateCanvasPanX = this.sWidth / devicePixelRatio;
|
|
631
|
-
const validateCanvasPanY = this.sHeight / devicePixelRatio;
|
|
632
|
-
const canvasPanX = validateCanvasPanX * (canvasX - 0.5);
|
|
633
|
-
const canvasPanY = validateCanvasPanY * (canvasY - 0.5);
|
|
634
|
-
const dimensions = imageData.getDimensions();
|
|
635
|
-
const canvasZero = this.worldToCanvas(imageData.indexToWorld([0, 0, 0]));
|
|
636
|
-
const canvasEdge = this.worldToCanvas(
|
|
637
|
-
imageData.indexToWorld([
|
|
638
|
-
dimensions[0] - 1,
|
|
639
|
-
dimensions[1] - 1,
|
|
640
|
-
dimensions[2],
|
|
641
|
-
])
|
|
642
|
-
);
|
|
643
|
-
const canvasImage = [
|
|
644
|
-
canvasEdge[0] - canvasZero[0],
|
|
645
|
-
canvasEdge[1] - canvasZero[1],
|
|
646
|
-
];
|
|
647
|
-
const [imgWidth, imgHeight] = canvasImage;
|
|
648
|
-
const [imageX, imageY] = imagePoint;
|
|
649
|
-
const imagePanX =
|
|
650
|
-
(zoom * imgWidth * (0.5 - imageX) * validateCanvasPanY) / imgHeight;
|
|
651
|
-
const imagePanY = zoom * validateCanvasPanY * (0.5 - imageY);
|
|
655
|
+
// make calculations relative to the fitToCanvasCamera view
|
|
656
|
+
const { _suppressCameraModifiedEvents } = this;
|
|
657
|
+
this._suppressCameraModifiedEvents = true;
|
|
652
658
|
|
|
653
|
-
|
|
654
|
-
|
|
659
|
+
// This should only apply for storeAsInitialCamera, but the calculations
|
|
660
|
+
// currently don't quite work otherwise.
|
|
661
|
+
// TODO - fix so that the store works for existing transforms
|
|
662
|
+
this.setCamera(this.fitToCanvasCamera);
|
|
655
663
|
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
this.
|
|
664
|
+
if (areaType === 'SCALE') {
|
|
665
|
+
this.setDisplayAreaScale(displayArea);
|
|
666
|
+
} else {
|
|
667
|
+
this.setInterpolationType(
|
|
668
|
+
this.getProperties().interpolationType || InterpolationType.LINEAR
|
|
669
|
+
);
|
|
670
|
+
this.setDisplayAreaFit(displayArea);
|
|
660
671
|
}
|
|
661
672
|
|
|
662
|
-
//
|
|
663
|
-
// then store the display area as the baseline display area.
|
|
673
|
+
// Set the initial camera if appropriate
|
|
664
674
|
if (storeAsInitialCamera) {
|
|
665
|
-
this.
|
|
675
|
+
this.initialCamera = this.getCamera();
|
|
666
676
|
}
|
|
667
677
|
|
|
668
|
-
|
|
678
|
+
// Restore event firing
|
|
679
|
+
this._suppressCameraModifiedEvents = _suppressCameraModifiedEvents;
|
|
680
|
+
if (!suppressEvents && !_suppressCameraModifiedEvents) {
|
|
669
681
|
const eventDetail: EventTypes.DisplayAreaModifiedEventDetail = {
|
|
670
682
|
viewportId: this.id,
|
|
671
683
|
displayArea: displayArea,
|
|
@@ -673,6 +685,156 @@ class Viewport implements IViewport {
|
|
|
673
685
|
};
|
|
674
686
|
|
|
675
687
|
triggerEvent(this.element, Events.DISPLAY_AREA_MODIFIED, eventDetail);
|
|
688
|
+
this.setCamera(this.getCamera());
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* Sets the viewport to pixel scaling mode. Pixel scaling displays
|
|
694
|
+
* 1 image pixel as 1 (or scale) physical screen pixels. That is,
|
|
695
|
+
* a 1024x512 image will be displayed with scale=2, as 2048x1024
|
|
696
|
+
* physical image pixels.
|
|
697
|
+
*
|
|
698
|
+
* @param displayArea - display area to set
|
|
699
|
+
* * displayArea.scale - the number of physical pixels to display
|
|
700
|
+
* each image pixel in. Values `< 1` mean smaller than physical,
|
|
701
|
+
* while values `> 1` mean more than one pixel. Default is 1
|
|
702
|
+
* Suggest using whole numbers or integer fractions (eg `1/3`)
|
|
703
|
+
*/
|
|
704
|
+
protected setDisplayAreaScale(displayArea: DisplayArea): void {
|
|
705
|
+
const { scale = 1 } = displayArea;
|
|
706
|
+
const canvas = this.canvas;
|
|
707
|
+
const height = canvas.height;
|
|
708
|
+
const width = canvas.width;
|
|
709
|
+
if (height < 8 || width < 8) {
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
const imageData = this.getDefaultImageData();
|
|
713
|
+
const spacingWorld = imageData.getSpacing();
|
|
714
|
+
const spacing = spacingWorld[1];
|
|
715
|
+
// Need nearest interpolation for scale
|
|
716
|
+
this.setInterpolationType(InterpolationType.NEAREST);
|
|
717
|
+
this.setCamera({ parallelScale: (height * spacing) / (2 * scale) });
|
|
718
|
+
|
|
719
|
+
// If this is scale, then image area isn't allowed, so just delete it to be safe
|
|
720
|
+
delete displayArea.imageArea;
|
|
721
|
+
// Apply the pan values from the display area.
|
|
722
|
+
this.setDisplayAreaFit(displayArea);
|
|
723
|
+
|
|
724
|
+
// Need to ensure the focal point is aligned with the canvas size/position
|
|
725
|
+
// so that we don't get half pixel rendering, which causes additional
|
|
726
|
+
// moire patterns to be displayed.
|
|
727
|
+
// This is based on the canvas size having the center pixel be at a fractional
|
|
728
|
+
// position when the size is even, so matching a fractional position on the
|
|
729
|
+
// focal point to the center of an image pixel.
|
|
730
|
+
const { focalPoint, position, viewUp, viewPlaneNormal } = this.getCamera();
|
|
731
|
+
const focalChange = vec3.create();
|
|
732
|
+
if (canvas.height % 2) {
|
|
733
|
+
vec3.scaleAndAdd(focalChange, focalChange, viewUp, scale * 0.5 * spacing);
|
|
734
|
+
}
|
|
735
|
+
if (canvas.width % 2) {
|
|
736
|
+
const viewRight = vec3.cross(vec3.create(), viewUp, viewPlaneNormal);
|
|
737
|
+
vec3.scaleAndAdd(
|
|
738
|
+
focalChange,
|
|
739
|
+
focalChange,
|
|
740
|
+
viewRight,
|
|
741
|
+
scale * 0.5 * spacing
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
if (!focalChange[0] && !focalChange[1] && !focalChange[2]) {
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
this.setCamera({
|
|
748
|
+
focalPoint: <Point3>vec3.add(vec3.create(), focalPoint, focalChange),
|
|
749
|
+
position: <Point3>vec3.add(vec3.create(), position, focalChange),
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* This applies a display area with a fit of the provided area to the
|
|
755
|
+
* available area.
|
|
756
|
+
* The zoom level is controlled by the imageArea parameter, which is a pair
|
|
757
|
+
* of percentage width in the horizontal and vertical dimension is scaled
|
|
758
|
+
* to fit the displayable area. Both values are taken into account, and the
|
|
759
|
+
* scaling is set so that both fractions of the image area are visible.
|
|
760
|
+
*
|
|
761
|
+
* The panning is controlled by the imageCanvasPoint, which has two
|
|
762
|
+
* values, teh imagePoint and the canvasPoint. They are fractional
|
|
763
|
+
* values of the image and canvas respectively, with the panning set to
|
|
764
|
+
* display the image pixel at the given fraction on top of the canvas at the
|
|
765
|
+
* given percentage. The default points are 0.5.
|
|
766
|
+
*
|
|
767
|
+
* For example, if the zoom level is [2,1], then the image is displayed
|
|
768
|
+
* such that at least twice the width is visible, and the height is visible.
|
|
769
|
+
* That will result in the image width being black, divided up on the left
|
|
770
|
+
* and right according to the imageCanvasPoint
|
|
771
|
+
*
|
|
772
|
+
* Then, if the imagePoint is [1,0] and the canvas point is [1,0], then
|
|
773
|
+
* the right most edge of the image, at the top of the image, will be
|
|
774
|
+
* displayed at the right most edge of the canvas, at the top.
|
|
775
|
+
*
|
|
776
|
+
*/
|
|
777
|
+
protected setDisplayAreaFit(displayArea: DisplayArea) {
|
|
778
|
+
const { imageArea, imageCanvasPoint } = displayArea;
|
|
779
|
+
|
|
780
|
+
const devicePixelRatio = window?.devicePixelRatio || 1;
|
|
781
|
+
const imageData = this.getDefaultImageData();
|
|
782
|
+
if (!imageData) {
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
const canvasWidth = this.sWidth / devicePixelRatio;
|
|
786
|
+
const canvasHeight = this.sHeight / devicePixelRatio;
|
|
787
|
+
const dimensions = imageData.getDimensions();
|
|
788
|
+
const canvasZero = this.worldToCanvas(imageData.indexToWorld([0, 0, 0]));
|
|
789
|
+
const canvasEdge = this.worldToCanvas(
|
|
790
|
+
imageData.indexToWorld([
|
|
791
|
+
dimensions[0] - 1,
|
|
792
|
+
dimensions[1] - 1,
|
|
793
|
+
dimensions[2],
|
|
794
|
+
])
|
|
795
|
+
);
|
|
796
|
+
|
|
797
|
+
const canvasImage = [
|
|
798
|
+
Math.abs(canvasEdge[0] - canvasZero[0]),
|
|
799
|
+
Math.abs(canvasEdge[1] - canvasZero[1]),
|
|
800
|
+
];
|
|
801
|
+
const [imgWidth, imgHeight] = canvasImage;
|
|
802
|
+
|
|
803
|
+
if (imageArea) {
|
|
804
|
+
const [areaX, areaY] = imageArea;
|
|
805
|
+
const requireX = Math.abs((areaX * imgWidth) / canvasWidth);
|
|
806
|
+
const requireY = Math.abs((areaY * imgHeight) / canvasHeight);
|
|
807
|
+
|
|
808
|
+
const initZoom = this.getZoom();
|
|
809
|
+
const fitZoom = this.getZoom(this.fitToCanvasCamera);
|
|
810
|
+
const absZoom = Math.min(1 / requireX, 1 / requireY);
|
|
811
|
+
const applyZoom = (absZoom * initZoom) / fitZoom;
|
|
812
|
+
this.setZoom(applyZoom, false);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// getting the image info
|
|
816
|
+
// getting the image info
|
|
817
|
+
if (imageCanvasPoint) {
|
|
818
|
+
const { imagePoint, canvasPoint = imagePoint || [0.5, 0.5] } =
|
|
819
|
+
imageCanvasPoint;
|
|
820
|
+
const [canvasX, canvasY] = canvasPoint;
|
|
821
|
+
const canvasPanX = canvasWidth * (canvasX - 0.5);
|
|
822
|
+
const canvasPanY = canvasHeight * (canvasY - 0.5);
|
|
823
|
+
|
|
824
|
+
const [imageX, imageY] = imagePoint || canvasPoint;
|
|
825
|
+
const useZoom = 1;
|
|
826
|
+
const imagePanX = useZoom * imgWidth * (0.5 - imageX);
|
|
827
|
+
const imagePanY = useZoom * imgHeight * (0.5 - imageY);
|
|
828
|
+
|
|
829
|
+
const newPositionX = imagePanX + canvasPanX;
|
|
830
|
+
const newPositionY = imagePanY + canvasPanY;
|
|
831
|
+
|
|
832
|
+
const deltaPoint2: Point2 = [newPositionX, newPositionY];
|
|
833
|
+
// Use getPan from current for the setting
|
|
834
|
+
vec2.add(deltaPoint2, deltaPoint2, this.getPan());
|
|
835
|
+
// The pan is part of the display area settings, not the initial camera, so
|
|
836
|
+
// don't store as initial camera here - that breaks rotation and other changes.
|
|
837
|
+
this.setPan(deltaPoint2, false);
|
|
676
838
|
}
|
|
677
839
|
}
|
|
678
840
|
|
|
@@ -715,7 +877,10 @@ class Viewport implements IViewport {
|
|
|
715
877
|
const focalPoint = <Point3>[0, 0, 0];
|
|
716
878
|
const imageData = this.getDefaultImageData();
|
|
717
879
|
|
|
718
|
-
//
|
|
880
|
+
// The bounds are used to set the clipping view, which is then used to
|
|
881
|
+
// figure out the center point of each image. This needs to be the depth
|
|
882
|
+
// center, so the bounds need to be extended by the spacing such that the
|
|
883
|
+
// depth center is in the middle of each image.
|
|
719
884
|
if (imageData) {
|
|
720
885
|
const spc = imageData.getSpacing();
|
|
721
886
|
|
|
@@ -741,9 +906,14 @@ class Viewport implements IViewport {
|
|
|
741
906
|
|
|
742
907
|
if (imageData) {
|
|
743
908
|
const dimensions = imageData.getDimensions();
|
|
909
|
+
// TODO: This should be the line below, but that causes issues with existing
|
|
910
|
+
// tests. Not doing that adds significant fuzziness on rendering, so at
|
|
911
|
+
// some point it should be fixed.
|
|
912
|
+
// const middleIJK = dimensions.map((d) => Math.floor((d-1) / 2));
|
|
744
913
|
const middleIJK = dimensions.map((d) => Math.floor(d / 2));
|
|
745
914
|
|
|
746
915
|
const idx = [middleIJK[0], middleIJK[1], middleIJK[2]];
|
|
916
|
+
// Modifies the focal point in place, as this hits the vtk indexToWorld function
|
|
747
917
|
imageData.indexToWorld(idx, focalPoint);
|
|
748
918
|
}
|
|
749
919
|
|
|
@@ -755,37 +925,20 @@ class Viewport implements IViewport {
|
|
|
755
925
|
const boundsAspectRatio = widthWorld / heightWorld;
|
|
756
926
|
const canvasAspectRatio = canvasSize[0] / canvasSize[1];
|
|
757
927
|
|
|
758
|
-
|
|
928
|
+
const scaleFactor = boundsAspectRatio / canvasAspectRatio;
|
|
759
929
|
|
|
760
|
-
|
|
761
|
-
// can fit full height, so use it.
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
const scaleFactor = boundsAspectRatio / canvasAspectRatio;
|
|
765
|
-
|
|
766
|
-
radius = (heightWorld * scaleFactor) / 2;
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
//const angle = vtkMath.radiansFromDegrees(activeCamera.getViewAngle())
|
|
770
|
-
const parallelScale = this.insetImageMultiplier * radius;
|
|
771
|
-
|
|
772
|
-
let w1 = bounds[1] - bounds[0];
|
|
773
|
-
let w2 = bounds[3] - bounds[2];
|
|
774
|
-
let w3 = bounds[5] - bounds[4];
|
|
775
|
-
w1 *= w1;
|
|
776
|
-
w2 *= w2;
|
|
777
|
-
w3 *= w3;
|
|
778
|
-
radius = w1 + w2 + w3;
|
|
930
|
+
const parallelScale =
|
|
931
|
+
scaleFactor < 1 // can fit full height, so use it.
|
|
932
|
+
? (this.insetImageMultiplier * heightWorld) / 2
|
|
933
|
+
: (this.insetImageMultiplier * heightWorld * scaleFactor) / 2;
|
|
779
934
|
|
|
780
935
|
// If we have just a single point, pick a radius of 1.0
|
|
781
|
-
radius = radius === 0 ? 1.0 : radius;
|
|
782
|
-
|
|
783
936
|
// compute the radius of the enclosing sphere
|
|
784
|
-
radius = Math.sqrt(radius) * 0.5;
|
|
785
|
-
|
|
786
937
|
// For 3D viewport, we should increase the radius to make sure the whole
|
|
787
938
|
// volume is visible and we don't get clipping artifacts.
|
|
788
|
-
radius =
|
|
939
|
+
const radius =
|
|
940
|
+
Viewport.boundsRadius(bounds) *
|
|
941
|
+
(this.type === ViewportType.VOLUME_3D ? 10 : 1);
|
|
789
942
|
|
|
790
943
|
const distance = this.insetImageMultiplier * radius;
|
|
791
944
|
|
|
@@ -893,19 +1046,19 @@ class Viewport implements IViewport {
|
|
|
893
1046
|
* computed from the current camera, where the initial pan
|
|
894
1047
|
* value is [0,0].
|
|
895
1048
|
*/
|
|
896
|
-
public getPan(): Point2 {
|
|
1049
|
+
public getPan(initialCamera = this.initialCamera): Point2 {
|
|
897
1050
|
const activeCamera = this.getVtkActiveCamera();
|
|
898
1051
|
const focalPoint = activeCamera.getFocalPoint() as Point3;
|
|
899
1052
|
|
|
900
1053
|
const zero3 = this.canvasToWorld([0, 0]);
|
|
901
1054
|
const initialCanvasFocal = this.worldToCanvas(
|
|
902
|
-
<Point3>vec3.subtract(
|
|
1055
|
+
<Point3>vec3.subtract([0, 0, 0], initialCamera.focalPoint, zero3)
|
|
903
1056
|
);
|
|
904
1057
|
const currentCanvasFocal = this.worldToCanvas(
|
|
905
|
-
<Point3>vec3.subtract(
|
|
1058
|
+
<Point3>vec3.subtract([0, 0, 0], focalPoint, zero3)
|
|
906
1059
|
);
|
|
907
1060
|
const result = <Point2>(
|
|
908
|
-
vec2.subtract(
|
|
1061
|
+
vec2.subtract([0, 0], initialCanvasFocal, currentCanvasFocal)
|
|
909
1062
|
);
|
|
910
1063
|
return result;
|
|
911
1064
|
}
|
|
@@ -926,7 +1079,7 @@ class Viewport implements IViewport {
|
|
|
926
1079
|
const previousCamera = this.getCamera();
|
|
927
1080
|
const { focalPoint, position } = previousCamera;
|
|
928
1081
|
const zero3 = this.canvasToWorld([0, 0]);
|
|
929
|
-
const delta2 = vec2.subtract(
|
|
1082
|
+
const delta2 = vec2.subtract([0, 0], pan, this.getPan());
|
|
930
1083
|
if (
|
|
931
1084
|
Math.abs(delta2[0]) < 1 &&
|
|
932
1085
|
Math.abs(delta2[1]) < 1 &&
|
|
@@ -956,9 +1109,9 @@ class Viewport implements IViewport {
|
|
|
956
1109
|
* originally applied to the image. That is, on initial display,
|
|
957
1110
|
* the zoom level is 1. Computed as a function of the camera.
|
|
958
1111
|
*/
|
|
959
|
-
public getZoom(): number {
|
|
1112
|
+
public getZoom(compareCamera = this.initialCamera): number {
|
|
960
1113
|
const activeCamera = this.getVtkActiveCamera();
|
|
961
|
-
const { parallelScale: initialParallelScale } =
|
|
1114
|
+
const { parallelScale: initialParallelScale } = compareCamera;
|
|
962
1115
|
return initialParallelScale / activeCamera.getParallelScale();
|
|
963
1116
|
}
|
|
964
1117
|
|
|
@@ -1397,6 +1550,15 @@ class Viewport implements IViewport {
|
|
|
1397
1550
|
return { widthWorld: maxX - minX, heightWorld: maxY - minY };
|
|
1398
1551
|
}
|
|
1399
1552
|
|
|
1553
|
+
/**
|
|
1554
|
+
* Gets a view target specifying WHAT a view is displaying,
|
|
1555
|
+
* allowing for checking if a given image is displayed or could be displayed
|
|
1556
|
+
* in a given viewport.
|
|
1557
|
+
* See getViewPresentation for HOW a view is displayed.
|
|
1558
|
+
*
|
|
1559
|
+
* @param viewRefSpecifier - choose an alternate view to be specified, typically
|
|
1560
|
+
* a different slice index in the same set of images.
|
|
1561
|
+
*/
|
|
1400
1562
|
public getViewReference(
|
|
1401
1563
|
viewRefSpecifier: ViewReferenceSpecifier = {}
|
|
1402
1564
|
): ViewReference {
|
|
@@ -1410,6 +1572,13 @@ class Viewport implements IViewport {
|
|
|
1410
1572
|
return target;
|
|
1411
1573
|
}
|
|
1412
1574
|
|
|
1575
|
+
/**
|
|
1576
|
+
* Find out if this viewport does or could show this view reference.
|
|
1577
|
+
*
|
|
1578
|
+
* @param options - allows specifying whether the view COULD display this with
|
|
1579
|
+
* some modification - either navigation or displaying as volume.
|
|
1580
|
+
* @returns true if the viewport could show this view reference
|
|
1581
|
+
*/
|
|
1413
1582
|
public isReferenceViewable(
|
|
1414
1583
|
viewRef: ViewReference,
|
|
1415
1584
|
options?: ReferenceCompatibleOptions
|
|
@@ -1437,6 +1606,83 @@ class Viewport implements IViewport {
|
|
|
1437
1606
|
return true;
|
|
1438
1607
|
}
|
|
1439
1608
|
|
|
1609
|
+
/**
|
|
1610
|
+
* Gets a view presentation information specifying HOW a viewport displays
|
|
1611
|
+
* something, but not what is being displayed.
|
|
1612
|
+
* See getViewReference to get information on WHAT is being displayed.
|
|
1613
|
+
*
|
|
1614
|
+
* This is intended to have information on how an image is presented to the user, without
|
|
1615
|
+
* specifying what image s displayed. All of this information is available
|
|
1616
|
+
* externally, but this method combines the parts of this that are appropriate
|
|
1617
|
+
* for remember or applying to other views, without necessarily needing to know
|
|
1618
|
+
* what all the atributes are. That differs from methods like getCamera which
|
|
1619
|
+
* fetch exact view details that are not likely to be identical between viewports
|
|
1620
|
+
* as they change sizes or apply to different images.
|
|
1621
|
+
*
|
|
1622
|
+
* Note that the results of this can be used on different viewports, for example,
|
|
1623
|
+
* the pan values can be applied to a volume viewport showing a CT, and a
|
|
1624
|
+
* stack viewport showing an ultrasound.
|
|
1625
|
+
*
|
|
1626
|
+
* The selector allows choosing which view presentation attributes to return.
|
|
1627
|
+
* Some default values are available from `Viewport.CameraViewPresentation` and
|
|
1628
|
+
* `Viewport.TransferViewPresentation`
|
|
1629
|
+
*
|
|
1630
|
+
* @param viewPresSel - select which attributes to display.
|
|
1631
|
+
*/
|
|
1632
|
+
public getViewPresentation(
|
|
1633
|
+
viewPresSel: ViewPresentationSelector = {
|
|
1634
|
+
rotation: true,
|
|
1635
|
+
displayArea: true,
|
|
1636
|
+
zoom: true,
|
|
1637
|
+
pan: true,
|
|
1638
|
+
}
|
|
1639
|
+
): ViewPresentation {
|
|
1640
|
+
const target: ViewPresentation = {};
|
|
1641
|
+
|
|
1642
|
+
const { rotation, displayArea, zoom, pan } = viewPresSel;
|
|
1643
|
+
if (rotation) {
|
|
1644
|
+
target.rotation = this.getRotation();
|
|
1645
|
+
}
|
|
1646
|
+
if (displayArea) {
|
|
1647
|
+
target.displayArea = this.getDisplayArea();
|
|
1648
|
+
}
|
|
1649
|
+
const initZoom = this.getZoom();
|
|
1650
|
+
|
|
1651
|
+
if (zoom) {
|
|
1652
|
+
target.zoom = initZoom;
|
|
1653
|
+
}
|
|
1654
|
+
if (pan) {
|
|
1655
|
+
target.pan = this.getPan();
|
|
1656
|
+
vec2.scale(target.pan, target.pan, 1 / initZoom);
|
|
1657
|
+
}
|
|
1658
|
+
return target;
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
/**
|
|
1662
|
+
* Sets the given view. This can apply both the view reference and view presentation
|
|
1663
|
+
* without getting multiple event notifications on shared values like camera updates or
|
|
1664
|
+
* flickers as multiple changes are applied.
|
|
1665
|
+
*
|
|
1666
|
+
* @param viewRef - the basic positioning in terms of what image id/slice index/orientation to display
|
|
1667
|
+
* * The viewRef must be applicable to the current stack or volume, otherwise an exception will be thrown
|
|
1668
|
+
* @param viewPres - the presentation information to apply to the current image (as chosen above)
|
|
1669
|
+
*/
|
|
1670
|
+
public setView(viewRef?: ViewReference, viewPres?: ViewPresentation) {
|
|
1671
|
+
if (viewPres) {
|
|
1672
|
+
const { displayArea, zoom = this.getZoom(), pan, rotation } = viewPres;
|
|
1673
|
+
if (displayArea !== this.getDisplayArea()) {
|
|
1674
|
+
this.setDisplayArea(displayArea);
|
|
1675
|
+
}
|
|
1676
|
+
this.setZoom(zoom);
|
|
1677
|
+
if (pan) {
|
|
1678
|
+
this.setPan(vec2.scale([0, 0], pan, zoom) as Point2);
|
|
1679
|
+
}
|
|
1680
|
+
if (rotation >= 0) {
|
|
1681
|
+
this.setRotation(rotation);
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1440
1686
|
protected _shouldUseNativeDataType() {
|
|
1441
1687
|
const { useNorm16Texture, preferSizeOverAccuracy } =
|
|
1442
1688
|
getConfiguration().rendering;
|
|
@@ -1556,6 +1802,22 @@ class Viewport implements IViewport {
|
|
|
1556
1802
|
[p7, p8],
|
|
1557
1803
|
];
|
|
1558
1804
|
}
|
|
1805
|
+
|
|
1806
|
+
/**
|
|
1807
|
+
* Computes the bounds radius value
|
|
1808
|
+
*/
|
|
1809
|
+
static boundsRadius(bounds: number[]) {
|
|
1810
|
+
const w1 = (bounds[1] - bounds[0]) ** 2;
|
|
1811
|
+
const w2 = (bounds[3] - bounds[2]) ** 2;
|
|
1812
|
+
const w3 = (bounds[5] - bounds[4]) ** 2;
|
|
1813
|
+
|
|
1814
|
+
// If we have just a single point, pick a radius of 1.0
|
|
1815
|
+
// compute the radius of the enclosing sphere
|
|
1816
|
+
// For 3D viewport, we should increase the radius to make sure the whole
|
|
1817
|
+
// volume is visible and we don't get clipping artifacts.
|
|
1818
|
+
const radius = Math.sqrt(w1 + w2 + w3 || 1) * 0.5;
|
|
1819
|
+
return radius;
|
|
1820
|
+
}
|
|
1559
1821
|
}
|
|
1560
1822
|
|
|
1561
1823
|
export default Viewport;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const VIEWPORT_ELEMENT = 'viewport-element';
|
|
2
2
|
const CANVAS_CSS_CLASS = 'cornerstone-canvas';
|
|
3
|
+
export const EPSILON = 1e-4;
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Create a canvas and append it to the element
|
|
@@ -13,6 +14,7 @@ function createCanvas(element: Element | HTMLDivElement): HTMLCanvasElement {
|
|
|
13
14
|
canvas.style.position = 'absolute';
|
|
14
15
|
canvas.style.width = '100%';
|
|
15
16
|
canvas.style.height = '100%';
|
|
17
|
+
canvas.style.imageRendering = 'pixelated';
|
|
16
18
|
canvas.classList.add(CANVAS_CSS_CLASS);
|
|
17
19
|
element.appendChild(canvas);
|
|
18
20
|
|
|
@@ -30,6 +32,8 @@ export function createViewportElement(element: HTMLDivElement): HTMLDivElement {
|
|
|
30
32
|
div.style.position = 'relative';
|
|
31
33
|
div.style.width = '100%';
|
|
32
34
|
div.style.height = '100%';
|
|
35
|
+
// Hide any canvas elements not viewable
|
|
36
|
+
div.style.overflow = 'hidden';
|
|
33
37
|
div.classList.add(VIEWPORT_ELEMENT);
|
|
34
38
|
element.appendChild(div);
|
|
35
39
|
|
|
@@ -39,6 +43,12 @@ export function createViewportElement(element: HTMLDivElement): HTMLDivElement {
|
|
|
39
43
|
/**
|
|
40
44
|
* Create a canvas or returns the one that already exists for a given element.
|
|
41
45
|
* It first checks if the element has a canvas, if not it creates one and returns it.
|
|
46
|
+
* The canvas is updated for:
|
|
47
|
+
* 1. width/height in screen pixels to completely cover the div element
|
|
48
|
+
* 2. CSS width/height in CSS pixels to be the size of the physical screen pixels
|
|
49
|
+
* width and height (from #1)
|
|
50
|
+
* This allows drawing to the canvas and having pixel perfect/exact drawing to
|
|
51
|
+
* the physical screen pixels.
|
|
42
52
|
*
|
|
43
53
|
* @param element - An HTML Element
|
|
44
54
|
* @returns canvas a Canvas DOM element
|
|
@@ -54,5 +64,26 @@ export default function getOrCreateCanvas(
|
|
|
54
64
|
const internalDiv =
|
|
55
65
|
element.querySelector(viewportElement) || createViewportElement(element);
|
|
56
66
|
|
|
57
|
-
|
|
67
|
+
const canvas = (internalDiv.querySelector(canvasSelector) ||
|
|
68
|
+
createCanvas(internalDiv)) as HTMLCanvasElement;
|
|
69
|
+
// Fit the canvas into the div
|
|
70
|
+
const rect = internalDiv.getBoundingClientRect();
|
|
71
|
+
const devicePixelRatio = window.devicePixelRatio || 1;
|
|
72
|
+
|
|
73
|
+
// The width/height is the number of physical pixels which will completely
|
|
74
|
+
// cover the div so that no pixels, fractional or full are left uncovered.
|
|
75
|
+
// Thus, it is the ceiling of the CSS size times the physical pixels.
|
|
76
|
+
// In theory, the physical pixels can be offset from CSS pixels, but in practice
|
|
77
|
+
// this hasn't been observed.
|
|
78
|
+
const width = Math.ceil(rect.width * devicePixelRatio);
|
|
79
|
+
const height = Math.ceil(rect.height * devicePixelRatio);
|
|
80
|
+
canvas.width = width;
|
|
81
|
+
canvas.height = height;
|
|
82
|
+
// Reset the size of the canvas to be the number of physical pixels,
|
|
83
|
+
// expressed as CSS pixels, with a tiny extra amount to prevent clipping
|
|
84
|
+
// to the next lower size in the physical display.
|
|
85
|
+
canvas.style.width = (width + EPSILON) / devicePixelRatio + 'px';
|
|
86
|
+
canvas.style.height = (height + EPSILON) / devicePixelRatio + 'px';
|
|
87
|
+
|
|
88
|
+
return canvas;
|
|
58
89
|
}
|
package/src/cache/cache.ts
CHANGED
|
@@ -918,7 +918,8 @@ class Cache implements ICache {
|
|
|
918
918
|
const imageCacheOffsetMap = volume.imageCacheOffsetMap;
|
|
919
919
|
|
|
920
920
|
if (imageCacheOffsetMap.size === 0) {
|
|
921
|
-
|
|
921
|
+
// This happens during testing and isn't an issue
|
|
922
|
+
// console.warn('No cached images to restore for this volume.');
|
|
922
923
|
return;
|
|
923
924
|
}
|
|
924
925
|
|