Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: WSI Viewport - basic framework to display WSI images with annotations #944

Merged
merged 86 commits into from
Jun 12, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
6bd88ec
feat: WSI Viewport - basic framework to display a dummy background
wayfarer3130 Dec 13, 2023
c131853
Load dicom-microscopy-viewer in WSI
wayfarer3130 Dec 13, 2023
0a12a13
fix: Basic load to attempt to view images
wayfarer3130 Dec 13, 2023
9d6fe59
Update magic incantations to allow WSI to run
wayfarer3130 Dec 13, 2023
6d7f4d3
Fix the build
wayfarer3130 Dec 13, 2023
3998302
Merge remote-tracking branch 'origin/main' into feat/wsi-viewport
wayfarer3130 Dec 13, 2023
8c72176
Merge remote-tracking branch 'origin/main' into feat/wsi-viewport
wayfarer3130 Dec 19, 2023
f403146
Start moving the viewport around
wayfarer3130 Dec 20, 2023
105a7a2
fix: Focal point getter is working for center focal at various zoom
wayfarer3130 Dec 22, 2023
d570081
fix: Make pan work with WSI viewport
wayfarer3130 Dec 22, 2023
f00706a
Merge remote-tracking branch 'origin/main' into feat/wsi-viewport
wayfarer3130 Jan 2, 2024
11e4cac
Fix panning for the WSI tool - the center point of the image is reversed
wayfarer3130 Jan 2, 2024
3481216
Add CS3D pan/zoom mapping
wayfarer3130 Jan 2, 2024
96cd98f
Add some annotation display capability - positioning is bad
wayfarer3130 Jan 2, 2024
8a72338
Update to include estimates
wayfarer3130 Jan 2, 2024
bc884b9
Update estimates to include multiple focal planes and additional
wayfarer3130 Jan 4, 2024
9c856a0
Merge remote-tracking branch 'origin/main' into feat/wsi-viewport
wayfarer3130 Jan 11, 2024
629d5d9
Merge remote-tracking branch 'origin/main' into feat/wsi-viewport
wayfarer3130 Feb 9, 2024
f6f6e34
Merge remote-tracking branch 'origin/main' into feat/wsi-viewport
wayfarer3130 Feb 11, 2024
5dbb635
Add full dynamic load on dicom-microscopy-viewer
wayfarer3130 Feb 11, 2024
9f98413
Update node version
wayfarer3130 Feb 11, 2024
d1a019a
fix: Positioning at full zoom
wayfarer3130 Feb 12, 2024
1f8366e
Fix pan/zoom
wayfarer3130 Feb 12, 2024
38958f8
Adding example runner with annotations
wayfarer3130 Feb 12, 2024
af67ea6
Merge remote-tracking branch 'origin/main' into feat/wsi-viewport
wayfarer3130 Feb 12, 2024
427fb1f
Add working version of microscopy viewer for server build
wayfarer3130 Feb 12, 2024
23f6895
Fix manipulation bindings/annotation tools deploy
wayfarer3130 Feb 12, 2024
32c6f6b
fix: Adding wsi annotation tools in properly
wayfarer3130 Feb 12, 2024
33dead9
Fix integration test with wasm
wayfarer3130 Feb 12, 2024
3a2248f
Change to cause rebuild
wayfarer3130 Feb 12, 2024
a7e3521
Ignore failing test
wayfarer3130 Feb 12, 2024
26be4a5
Comment out failing test
wayfarer3130 Feb 12, 2024
19d32d8
Temporary disabling contour interpolation tests
wayfarer3130 Feb 12, 2024
7cb4f33
Try to change the template to make things work
wayfarer3130 Feb 13, 2024
b226766
Change path to relative for dicom-microscopy-viewer import
wayfarer3130 Feb 13, 2024
ca84b43
Fix examples build
wayfarer3130 Feb 13, 2024
75f3ae6
Fix examples demo issue
wayfarer3130 Feb 13, 2024
6d233e1
Fix deployment
wayfarer3130 Feb 13, 2024
6352e10
Fix the import path
wayfarer3130 Feb 13, 2024
f53a4a5
fix netlify deploy
wayfarer3130 Feb 13, 2024
12f28c7
Merge remote-tracking branch 'origin/main' into feat/wsi-viewport
wayfarer3130 Feb 13, 2024
c040876
api-check
wayfarer3130 Feb 13, 2024
6894cc3
Merge remote-tracking branch 'origin/main' into feat/wsi-viewport
wayfarer3130 Feb 13, 2024
96ee59e
Merge remote-tracking branch 'origin/main' into feat/wsi-viewport
wayfarer3130 Feb 20, 2024
a0f98f7
Fixes for [#1090] to not show context menu and add background
wayfarer3130 Feb 20, 2024
24e2a97
Merge remote-tracking branch 'origin/main' into feat/wsi-viewport
wayfarer3130 May 1, 2024
bea6c71
fix: Use a newer version of dicom-microscopy-viewer and load it dynam…
wayfarer3130 May 1, 2024
b48d6cf
Build change
wayfarer3130 May 1, 2024
bdce4cd
Merge remote-tracking branch 'origin/main' into feat/wsi-viewport
wayfarer3130 May 28, 2024
b26cdd8
Update dcmjs version
wayfarer3130 Mar 7, 2024
1300c68
Updated to later microscopy viewer to ensure compatibility with OHIF
wayfarer3130 May 28, 2024
52faafb
Fix basic display
wayfarer3130 May 29, 2024
a697760
Merge remote-tracking branch 'origin/main' into feat/wsi-viewport
wayfarer3130 May 29, 2024
ed2c8e1
Fix video viewport getNumberOfSlices to return a value earlier
wayfarer3130 May 29, 2024
d1a70d7
Merge remote-tracking branch 'origin/main' into feat/wsi-viewport
wayfarer3130 May 30, 2024
3b4a1c6
Updating the externals to match how OHIF does it
wayfarer3130 May 30, 2024
1e796e9
Update the yarn lock to try to get the build workign
wayfarer3130 May 30, 2024
38ce062
Dropped node version back to 16
wayfarer3130 May 30, 2024
27a18f0
Undid some unneeded changes
wayfarer3130 May 30, 2024
45efdf8
Addign packageManager declaration
wayfarer3130 May 30, 2024
7c8da0f
Use resolutions
wayfarer3130 May 30, 2024
442def9
Trying to force an install anyways
wayfarer3130 May 30, 2024
8e34944
verbose install
wayfarer3130 May 30, 2024
3c241b0
Add a install:yml target
wayfarer3130 May 30, 2024
5990898
Yet another install location, trying to find the build location
wayfarer3130 May 30, 2024
0829afd
Remove some yarn flags to see if it installs
wayfarer3130 May 30, 2024
46c3bc1
Add a force install option
wayfarer3130 May 30, 2024
16d5ddf
Change to postinstall for adding tarball dependencies
wayfarer3130 May 31, 2024
eb7aa4b
Switch to node 20.14
wayfarer3130 May 31, 2024
3980c8c
Exclude pure lockfile tests
wayfarer3130 May 31, 2024
c0d5135
Use external library docs to force build
wayfarer3130 May 31, 2024
8946a1d
Changed addOns to match design in OHIF
wayfarer3130 May 31, 2024
6e59811
Fix install
wayfarer3130 May 31, 2024
a5d2e58
Merge remote-tracking branch 'origin/main' into feat/wsi-viewport
wayfarer3130 May 31, 2024
9f95d46
Update a couple of components that have issues
wayfarer3130 May 31, 2024
745c74e
Merge remote-tracking branch 'origin/main' into feat/wsi-viewport
wayfarer3130 May 31, 2024
7f1d3a4
Merge remote-tracking branch 'origin/main' into feat/wsi-viewport
wayfarer3130 Jun 10, 2024
aa32fb1
Remove tarball microscopy viewer
wayfarer3130 Jun 10, 2024
909c12e
Fix split tags
wayfarer3130 Jun 11, 2024
77201fa
Error handlign
wayfarer3130 Jun 11, 2024
c97f02c
PR review fixes
wayfarer3130 Jun 11, 2024
a66abb6
fix wsi test
wayfarer3130 Jun 12, 2024
4fa2c80
Fix the annotation measurements
wayfarer3130 Jun 12, 2024
6b8ce67
Fix deploy
wayfarer3130 Jun 12, 2024
887c532
Fix planar freehand
wayfarer3130 Jun 12, 2024
f04d64c
api-check
wayfarer3130 Jun 12, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .webpack/webpack.base.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ module.exports = (env, argv, { DIST_DIR }) => {
alias: {
'@cornerstonejs/dicom-image-loader':
'@cornerstonejs/dicom-image-loader/dist/dynamic-import/cornerstoneDICOMImageLoader.min.js',
'dicom-microscopy-viewer': 'dicom-microscopy-viewer/dist/dynamic-import/dicomMicroscopyViewer.min.js',
},
fallback: {
fs: false,
Expand Down
138 changes: 133 additions & 5 deletions common/reviews/api/core.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1745,8 +1745,6 @@ interface IRenderingEngine {
// (undocumented)
getStackViewports(): Array<IStackViewport>;
// (undocumented)
getVideoViewports(): Array<IVideoViewport>;
// (undocumented)
getViewport(id: string): IViewport;
// (undocumented)
getViewports(): Array<IViewport>;
Expand Down Expand Up @@ -2169,6 +2167,28 @@ interface IVolumeViewport extends IViewport {
worldToCanvas: (worldPos: Point3) => Point2;
}

// @public (undocumented)
interface IWSIViewport extends IViewport {
// (undocumented)
getCurrentImageId(): string;
// (undocumented)
getFrameNumber(): number;
// (undocumented)
getProperties: () => WSIViewportProperties;
// (undocumented)
resetCamera(resetPan?: boolean, resetZoom?: boolean): boolean;
// (undocumented)
resetProperties(): void;
// (undocumented)
resize: () => void;
// (undocumented)
setFrameNumber(frameNo: number): any;
// (undocumented)
setProperties(props: WSIViewportProperties, suppressEvents?: boolean): void;
// (undocumented)
setWSI: (imageIds: string[], client: any) => Promise<unknown>;
}

// @public (undocumented)
function linePlaneIntersection(p0: Point3, p1: Point3, plane: Plane): Point3;

Expand Down Expand Up @@ -2478,8 +2498,6 @@ export class RenderingEngine implements IRenderingEngine {
// (undocumented)
getStackViewports(): Array<IStackViewport>;
// (undocumented)
getVideoViewports(): Array<IVideoViewport>;
// (undocumented)
getViewport(viewportId: string): IViewport;
// (undocumented)
getViewports(): Array<IViewport>;
Expand Down Expand Up @@ -2910,6 +2928,7 @@ declare namespace Types {
ICamera,
IStackViewport,
IVideoViewport,
IWSIViewport,
IVolumeViewport,
IEnabledElement,
ICache,
Expand Down Expand Up @@ -2959,6 +2978,7 @@ declare namespace Types {
Plane,
ViewportInputOptions,
VideoViewportProperties,
WSIViewportProperties,
VOIRange,
VOI,
DisplayArea,
Expand Down Expand Up @@ -3000,6 +3020,7 @@ declare namespace Types {
ImageLoadListener,
InternalVideoCamera,
VideoViewportInput,
WSIViewportInput,
BoundsIJK,
Color,
ColorLUT
Expand Down Expand Up @@ -3468,7 +3489,9 @@ enum ViewportType {
// (undocumented)
VIDEO = "video",
// (undocumented)
VOLUME_3D = "volume3d"
VOLUME_3D = "volume3d",
// (undocumented)
WSI = "wsi"
}

// @public (undocumented)
Expand Down Expand Up @@ -3710,6 +3733,111 @@ declare namespace windowLevel {
// @public (undocumented)
function worldToImageCoords(imageId: string, worldCoords: Point3): Point2 | undefined;

// @public (undocumented)
export class WSIViewport extends Viewport implements IWSIViewport {
constructor(props: WSIViewportInput);
// (undocumented)
protected canvasToIndex: (canvasPos: Point2) => Point2;
// (undocumented)
canvasToWorld: (canvasPos: Point2) => Point3;
// (undocumented)
customRenderViewportToCanvas: () => void;
// (undocumented)
getCamera(): ICamera;
// (undocumented)
getCurrentImageId(): string;
// (undocumented)
getFrameNumber(): number;
// (undocumented)
getFrameOfReferenceUID: () => string;
// (undocumented)
getImageData(): {
dimensions: any;
spacing: any;
origin: any;
direction: any;
metadata: {
Modality: any;
};
getScalarData: () => any;
imageData: {
getDirection: () => any;
getDimensions: () => any;
getRange: () => number[];
getScalarData: () => any;
getSpacing: () => any;
worldToIndex: (point: Point3) => number[];
indexToWorld: (point: Point3) => Point3;
};
hasPixelSpacing: boolean;
calibration: IImageCalibration;
preScale: {
scaled: boolean;
};
};
// (undocumented)
getNumberOfSlices: () => number;
// (undocumented)
getPan(): Point2;
// (undocumented)
getProperties: () => WSIViewportProperties;
// (undocumented)
getRotation: () => number;
// (undocumented)
protected getScalarData(): any;
// (undocumented)
protected getTransform(): Transform;
// (undocumented)
hasImageURI(imageURI: string): boolean;
// (undocumented)
protected imageIds: string[];
// (undocumented)
protected indexToCanvas: (indexPos: Point2) => Point2;
// (undocumented)
protected metadata: any;
// (undocumented)
modality: any;
// (undocumented)
readonly renderingEngineId: string;
// (undocumented)
resetCamera: () => boolean;
// (undocumented)
resetProperties(): void;
// (undocumented)
resize: () => void;
// (undocumented)
setCamera(camera: ICamera): void;
// (undocumented)
setFrameNumber(frame: number): Promise<void>;
// (undocumented)
setProperties(props: WSIViewportProperties): void;
// (undocumented)
setWSI(imageIds: string[], client: any): Promise<void>;
// (undocumented)
readonly uid: any;
// (undocumented)
static get useCustomRenderingPipeline(): boolean;
// (undocumented)
worldToCanvas: (worldPos: Point3) => Point2;
}

// @public (undocumented)
type WSIViewportInput = {
id: string;
renderingEngineId: string;
type: ViewportType;
element: HTMLDivElement;
sx: number;
sy: number;
sWidth: number;
sHeight: number;
defaultOptions: any;
canvas: HTMLCanvasElement;
};

// @public (undocumented)
type WSIViewportProperties = ViewportProperties;

// (No @packageDocumentation comment for this package)

```
2 changes: 2 additions & 0 deletions karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ module.exports = function (config) {
'@cornerstonejs/streaming-image-volume-loader': path.resolve(
'packages/streaming-image-volume-loader/src/index'
),
'dicom-microscopy-viewer':
'dicom-microscopy-viewer/dist/dynamic-import/dicomMicroscopyViewer.min.js',
},
},
},
Expand Down
122 changes: 122 additions & 0 deletions packages/core/examples/wsi/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import {
RenderingEngine,
Types,
Enums,
getRenderingEngine,
} from '@cornerstonejs/core';
import dicomImageLoader from '@cornerstonejs/dicom-image-loader';
import { api } from 'dicomweb-client';

import {
initDemo,
setTitleAndDescription,
createImageIdsAndCacheMetaData,
getLocalUrl,
} from '../../../../utils/demo/helpers';

// This is for debugging purposes
console.warn(
'Click on index.ts to open source code for this example --------->'
);

const { wadors } = dicomImageLoader;

const { ViewportType, Events } = Enums;

// ======== Constants ======= //
const renderingEngineId = 'myRenderingEngine';
const viewportId = 'videoViewport';

// ======== Set up page ======== //
setTitleAndDescription(
'WSI - Whole Slide Imaging Viewport',
'Demonstrates viewing of whole slide imaging data'
);

const content = document.getElementById('content');
const element = document.createElement('div');
element.id = 'cornerstone-element';
element.style.width = '500px';
element.style.height = '500px';

content.appendChild(element);

const info = document.createElement('div');
content.appendChild(info);

const rotationInfo = document.createElement('div');
info.appendChild(rotationInfo);

const flipHorizontalInfo = document.createElement('div');
info.appendChild(flipHorizontalInfo);

const flipVerticalInfo = document.createElement('div');
info.appendChild(flipVerticalInfo);

element.addEventListener(Events.CAMERA_MODIFIED, (_) => {
// Get the rendering engine
const renderingEngine = getRenderingEngine(renderingEngineId);

// Get the stack viewport
const viewport = <Types.IStackViewport>(
renderingEngine.getViewport(viewportId)
);

if (!viewport) {
return;
}

const { flipHorizontal, flipVertical } = viewport.getCamera();
const { rotation } = viewport.getProperties();

rotationInfo.innerText = `Rotation: ${Math.round(rotation)}`;
flipHorizontalInfo.innerText = `Flip horizontal: ${flipHorizontal}`;
flipVerticalInfo.innerText = `Flip vertical: ${flipVertical}`;
});

/**
* Runs the demo
*/
async function run() {
// Init Cornerstone and related libraries
await initDemo();

// Get Cornerstone imageIds and fetch metadata into RAM
// TODO - deploy the testdata publically
const wadoRsRoot =
getLocalUrl() || 'https://d3t6nz73ql33tx.cloudfront.net/dicomweb';
const client = new api.DICOMwebClient({ url: wadoRsRoot });
const imageIds = await createImageIdsAndCacheMetaData({
StudyInstanceUID: '1.2.276.1.74.1.2.132733202464108492637644434464108492',
SeriesInstanceUID:
'2.16.840.1.113883.3.8467.132733202477512857637644434477512857',
client,
wadoRsRoot,
convertMultiframe: false,
});

// Instantiate a rendering engine
const renderingEngine = new RenderingEngine(renderingEngineId);

// Create a stack viewport

const viewportInput = {
viewportId,
type: ViewportType.WSI,
element,
defaultOptions: {
background: <Types.Point3>[0.2, 0, 0.2],
},
};

renderingEngine.enableElement(viewportInput);

// Get the stack viewport that was created
const viewport = <Types.IWSIViewport>renderingEngine.getViewport(viewportId);

client.getDICOMwebMetadata = (imageId) => wadors.metaDataManager.get(imageId);
// Set the stack on the viewport
await viewport.setWSI(imageIds, client);
}

run();
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@kitware/vtk.js": "27.3.1",
"comlink": "^4.4.1",
"detect-gpu": "^5.0.22",
"dicom-microscopy-viewer": "^0.45.1",
"gl-matrix": "^3.4.3",
"lodash.clonedeep": "4.5.0"
},
Expand Down
24 changes: 2 additions & 22 deletions packages/core/src/RenderingEngine/RenderingEngine.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Events from '../enums/Events';
import renderingEngineCache from './renderingEngineCache';
import eventTarget from '../eventTarget';
import { triggerEvent, uuidv4 } from '../utilities';
import { triggerEvent } from '../utilities';
import { vtkOffscreenMultiRenderWindow } from './vtkClasses';
import ViewportType from '../enums/ViewportType';
import VolumeViewport from './VolumeViewport';
Expand All @@ -11,11 +11,9 @@ import viewportTypeUsesCustomRenderingPipeline from './helpers/viewportTypeUsesC
import getOrCreateCanvas from './helpers/getOrCreateCanvas';
import { getShouldUseCPURendering, isCornerstoneInitialized } from '../init';
import type IStackViewport from '../types/IStackViewport';
import type IVideoViewport from '../types/IVideoViewport';
import type IRenderingEngine from '../types/IRenderingEngine';
import type IVolumeViewport from '../types/IVolumeViewport';
import type { IViewport } from '../types/IViewport';
import VideoViewport from './VideoViewport';
import viewportTypeToViewportClass from './helpers/viewportTypeToViewportClass';

import type * as EventTypes from '../types/EventTypes';
Expand Down Expand Up @@ -88,7 +86,7 @@ class RenderingEngine implements IRenderingEngine {
* @param uid - Unique identifier for RenderingEngine
*/
constructor(id?: string) {
this.id = id ? id : uuidv4();
this.id = id ? id : crypto.randomUUID();
wayfarer3130 marked this conversation as resolved.
Show resolved Hide resolved
this.useCPURendering = getShouldUseCPURendering();

renderingEngineCache.set(this);
Expand Down Expand Up @@ -382,24 +380,6 @@ class RenderingEngine implements IRenderingEngine {
return viewports.filter(isStackViewport) as Array<IStackViewport>;
}
Copy link
Member

@sedghi sedghi Dec 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why you are removing video viewport

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method was unused, and I don't see a need for methods for each type when we have the generic ones.


/**
* Filters all the available viewports and return the stack viewports
* @returns stack viewports registered on the rendering Engine
*/
public getVideoViewports(): Array<IVideoViewport> {
this._throwIfDestroyed();

const viewports = this.getViewports();

const isVideoViewport = (
viewport: IViewport
): viewport is VideoViewport => {
return viewport instanceof VideoViewport;
};

return viewports.filter(isVideoViewport) as Array<IVideoViewport>;
}

/**
* Return all the viewports that are volume viewports
* @returns An array of VolumeViewport objects.
Expand Down
Loading