Skip to content

Commit

Permalink
feat(overlayGrid): New overlay grid tool (#790)
Browse files Browse the repository at this point in the history
* Add OverlayGridTool

* Add comments

* Add sourceViewportIds in tool

* Transform annotations in a map and update api

* Refactor example imports

* Pre calculate reference line info

* update api

* Refactor tool

* refactor and clean

* add api checks

---------

Co-authored-by: Alireza <ar.sedghi@gmail.com>
  • Loading branch information
rodrigobasilio2022 and sedghi authored Sep 25, 2023
1 parent d0d2fac commit c8c5c91
Show file tree
Hide file tree
Showing 9 changed files with 630 additions and 0 deletions.
2 changes: 2 additions & 0 deletions common/reviews/api/streaming-image-volume-loader.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { default as default_2 } from 'packages/core/dist/esm/enums/RequestType';
import type { GetGPUTier } from 'detect-gpu';
import type { mat4 } from 'gl-matrix';
import type { TierResult } from 'detect-gpu';
import { vec3 } from 'gl-matrix';
import type vtkActor from '@kitware/vtk.js/Rendering/Core/Actor';
import type { vtkImageData } from '@kitware/vtk.js/Common/DataModel/ImageData';
import vtkImageSlice from '@kitware/vtk.js/Rendering/Core/ImageSlice';
Expand Down Expand Up @@ -575,6 +576,7 @@ enum GeometryType {
// @public (undocumented)
export const helpers: {
getDynamicVolumeInfo: typeof getDynamicVolumeInfo;
sortImageIdsAndGetSpacing: typeof sortImageIdsAndGetSpacing;
};

// @public (undocumented)
Expand Down
30 changes: 30 additions & 0 deletions common/reviews/api/tools.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -3617,6 +3617,36 @@ type OrientationVectors = {
viewUp: Point3;
};

// @public (undocumented)
export class OverlayGridTool extends AnnotationDisplayTool {
constructor(toolProps?: PublicToolProps, defaultToolProps?: ToolProps);
// (undocumented)
calculateImageIdPointSets: (imageId: string) => {
pointSet1: Types_2.Point3[];
pointSet2: Types_2.Point3[];
};
// (undocumented)
_init: () => void;
// (undocumented)
isDrawing: boolean;
// (undocumented)
isHandleOutsideImage: boolean;
// (undocumented)
mouseDragCallback: any;
// (undocumented)
onSetToolActive: () => void;
// (undocumented)
onSetToolEnabled: () => void;
// (undocumented)
renderAnnotation: (enabledElement: Types_2.IEnabledElement, svgDrawingHelper: SVGDrawingHelper) => boolean;
// (undocumented)
_throttledCalculateCachedStats: any;
// (undocumented)
static toolName: any;
// (undocumented)
touchDragCallback: any;
}

// @public (undocumented)
export class PaintFillTool extends BaseTool {
constructor(toolProps?: PublicToolProps, defaultToolProps?: ToolProps);
Expand Down
2 changes: 2 additions & 0 deletions packages/streaming-image-volume-loader/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import cornerstoneStreamingDynamicImageVolumeLoader from './cornerstoneStreaming
import StreamingImageVolume from './StreamingImageVolume';
import StreamingDynamicImageVolume from './StreamingDynamicImageVolume';
import getDynamicVolumeInfo from './helpers/getDynamicVolumeInfo';
import { sortImageIdsAndGetSpacing } from './helpers';

const helpers = {
getDynamicVolumeInfo,
sortImageIdsAndGetSpacing,
};

export {
Expand Down
191 changes: 191 additions & 0 deletions packages/tools/examples/overlayGrid/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import { RenderingEngine, Types, Enums } from '@cornerstonejs/core';
import {
initDemo,
createImageIdsAndCacheMetaData,
setTitleAndDescription,
} from '../../../../utils/demo/helpers';
import * as cornerstoneTools from '@cornerstonejs/tools';
import { sortImageIds } from './utils';

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

const {
ToolGroupManager,
Enums: csToolsEnums,
OverlayGridTool,
ZoomTool,
PanTool,
StackScrollMouseWheelTool,
} = cornerstoneTools;

const { MouseBindings } = csToolsEnums;
const { ViewportType } = Enums;

async function getImageStacks() {
// Get Cornerstone imageIds for the source data and fetch metadata into RAM
const wadoRsRoot = 'https://d33do7qe4w26qo.cloudfront.net/dicomweb';
const studyInstanceUID =
'1.3.6.1.4.1.25403.345050719074.3824.20170125095258.1';
const seriesInstanceUIDs = [
'1.3.6.1.4.1.25403.345050719074.3824.20170125095258.7',
'1.3.6.1.4.1.25403.345050719074.3824.20170125095319.5',
'1.3.6.1.4.1.25403.345050719074.3824.20170125095312.3',
];
const axialImageIds = await createImageIdsAndCacheMetaData({
StudyInstanceUID: studyInstanceUID,
SeriesInstanceUID: seriesInstanceUIDs[0],
wadoRsRoot,
});

const sagittalImageIds = await createImageIdsAndCacheMetaData({
StudyInstanceUID: studyInstanceUID,
SeriesInstanceUID: seriesInstanceUIDs[1],
wadoRsRoot,
});

const coronalImageIds = await createImageIdsAndCacheMetaData({
StudyInstanceUID: studyInstanceUID,
SeriesInstanceUID: seriesInstanceUIDs[2],
wadoRsRoot,
});

const imageStacks = [
sortImageIds(axialImageIds),
sortImageIds(sagittalImageIds),
sortImageIds(coronalImageIds),
];
return imageStacks;
}

const toolGroupId = 'MY_TOOLGROUP_ID';

// ======== Set up page ======== //
setTitleAndDescription(
'OverlayGrid',
'Here we demonstrate overlay grid tool working. The reference lines for all the images in axial series is displayed in the sagittal and coronal series.'
);

const size = '500px';
const content = document.getElementById('content');
const viewportGrid = document.createElement('div');

viewportGrid.style.display = 'flex';
viewportGrid.style.display = 'flex';
viewportGrid.style.flexDirection = 'row';

const element1 = document.createElement('div');
const element2 = document.createElement('div');
const element3 = document.createElement('div');
const elements = [element1, element2, element3];

for (let i = 0; i < elements.length; i++) {
elements[i].style.width = size;
elements[i].style.height = size;
// Disable right click context menu so we can have right click tools
elements[i].oncontextmenu = (e) => e.preventDefault();
viewportGrid.appendChild(elements[i]);
}

content.appendChild(viewportGrid);

const instructions = document.createElement('p');
instructions.innerText = `
Basic controls:
- Left click : pan images
- Right click : zoom images
`;

content.append(instructions);

// ============================= //

const viewportIds = ['CT_AXIAL', 'CT_SAGITTAL', 'CT_CORONAL'];

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

// Add tools to Cornerstone3D
cornerstoneTools.addTool(StackScrollMouseWheelTool);
cornerstoneTools.addTool(OverlayGridTool);
cornerstoneTools.addTool(PanTool);
cornerstoneTools.addTool(ZoomTool);

// Instantiate a rendering engine
const renderingEngineId = 'myRenderingEngine';
const renderingEngine = new RenderingEngine(renderingEngineId);
const orientations = [
Enums.OrientationAxis.AXIAL,
Enums.OrientationAxis.SAGITTAL,
Enums.OrientationAxis.CORONAL,
];

// Create the viewports
const viewportInputArray = [];
for (let i = 0; i < elements.length; i++) {
viewportInputArray[i] = {
viewportId: viewportIds[i],
type: ViewportType.STACK,
element: elements[i],
defaultOptions: {
orientation: orientations[i],
background: <Types.Point3>[0, 0, 0],
},
};
}
renderingEngine.setViewports(viewportInputArray);

const imageStacks = await getImageStacks();

// Define tool groups to add the segmentation display tool to
const toolGroup = ToolGroupManager.createToolGroup(toolGroupId);

const viewports = [];
for (let i = 0; i < viewportIds.length; i++) {
toolGroup.addViewport(viewportIds[i], renderingEngineId);
viewports[i] = <Types.IStackViewport>(
renderingEngine.getViewport(viewportIds[i])
);

await viewports[i].setStack(
imageStacks[i],
Math.floor(imageStacks[i].length / 2)
);
}

// Manipulation Tools
toolGroup.addTool(StackScrollMouseWheelTool.toolName);
toolGroup.addTool(OverlayGridTool.toolName, {
sourceImageIds: imageStacks[0],
});
toolGroup.addTool(ZoomTool.toolName);
toolGroup.addTool(PanTool.toolName);

toolGroup.setToolEnabled(OverlayGridTool.toolName);
toolGroup.setToolActive(StackScrollMouseWheelTool.toolName);
toolGroup.setToolActive(PanTool.toolName, {
bindings: [
{
mouseButton: MouseBindings.Primary, // Left Click
},
],
});
toolGroup.setToolActive(ZoomTool.toolName, {
bindings: [
{
mouseButton: MouseBindings.Secondary, // Right Click
},
],
});

// Render the image
renderingEngine.renderViewports(viewportIds);
}

run();
40 changes: 40 additions & 0 deletions packages/tools/examples/overlayGrid/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { metaData } from '@cornerstonejs/core';
import { helpers } from '@cornerstonejs/streaming-image-volume-loader';
import { vec3 } from 'gl-matrix';

/**
* Calculates the plane normal given the image orientation vector
* @param imageOrientation
* @returns
*/
export function calculatePlaneNormal(imageOrientation) {
const rowCosineVec = vec3.fromValues(
imageOrientation[0],
imageOrientation[1],
imageOrientation[2]
);
const colCosineVec = vec3.fromValues(
imageOrientation[3],
imageOrientation[4],
imageOrientation[5]
);
return vec3.cross(vec3.create(), rowCosineVec, colCosineVec);
}

/**
* Sort a imageId list
* @param imageIds
* @returns
*/
export function sortImageIds(imageIds) {
const { imageOrientationPatient } = metaData.get(
'imagePlaneModule',
imageIds[0]
);
const scanAxisNormal = calculatePlaneNormal(imageOrientationPatient);
const { sortedImageIds } = helpers.sortImageIdsAndGetSpacing(
imageIds,
scanAxisNormal
);
return sortedImageIds;
}
2 changes: 2 additions & 0 deletions packages/tools/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import {
ReferenceLines,
PaintFillTool,
ScaleOverlayTool,
OverlayGridTool,
} from './tools';

import * as Enums from './enums';
Expand Down Expand Up @@ -88,6 +89,7 @@ export {
LengthTool,
CrosshairsTool,
ReferenceLinesTool,
OverlayGridTool,
ProbeTool,
RectangleROITool,
EllipticalROITool,
Expand Down
Loading

0 comments on commit c8c5c91

Please sign in to comment.