Skip to content

Commit

Permalink
Create getContoursForSlices function
Browse files Browse the repository at this point in the history
  • Loading branch information
rodrigobasilio2022 committed Jan 26, 2024
1 parent a798596 commit e4a0277
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 19 deletions.
79 changes: 60 additions & 19 deletions packages/tools/examples/surfaceRenderingStack/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
setTitleAndDescription,
addToggleButtonToToolbar,
addSliderToToolbar,
addButtonToToolbar,
} from '../../../../utils/demo/helpers';
import * as cornerstoneTools from '@cornerstonejs/tools';
import { sortImageIds } from './utils';
Expand All @@ -22,6 +23,23 @@ import surface15 from '../../../../utils/assets/surfaces/lung15.json';
import surface16 from '../../../../utils/assets/surfaces/lung16.json';
import surface17 from '../../../../utils/assets/surfaces/lung17.json';

const volumeFlag = true;
let renderingEngine;
const viewportId1 = 'CT_AXIAL';
const viewportId2 = 'CT_VOLUME_AXIAL';
const viewportId3 = 'CT_VOLUME_SAG';

export function downloadObjectAsJson(exportObj, exportName) {
const dataStr =
'data:text/json;charset=utf-8,' +
encodeURIComponent(JSON.stringify(exportObj));
const downloadAnchorNode = document.createElement('a');
downloadAnchorNode.setAttribute('href', dataStr);
downloadAnchorNode.setAttribute('download', exportName + '.json');
document.body.appendChild(downloadAnchorNode); // required for firefox
downloadAnchorNode.click();
downloadAnchorNode.remove();
}
// This is for debugging purposes
console.warn(
'Click on index.ts to open source code for this example --------->'
Expand Down Expand Up @@ -119,6 +137,31 @@ addSliderToToolbar({
},
});

addButtonToToolbar({
title: 'Generate contours',
onClick: async () => {
if (volumeFlag) {
const viewport = <Types.IVolumeViewport>(
renderingEngine.getViewport(viewportId2)
);
const contours = await cornerstoneTools.utilities.getContoursForSlices(
surfaces[4].closedSurface.data,
viewport
);
downloadObjectAsJson(contours, 'contoursVolume');
} else {
const viewport = <Types.IStackViewport>(
renderingEngine.getViewport(viewportId1)
);
const contours = await cornerstoneTools.utilities.getContoursForSlices(
surfaces[4].closedSurface.data,
viewport
);
downloadObjectAsJson(contours, 'contoursStack');
}
},
});

//const surfaces = [surface];
const surfaces = [surface13, surface14, surface15, surface16, surface17];
async function addSegmentationsToState() {
Expand Down Expand Up @@ -233,13 +276,9 @@ async function run() {

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

// Create the viewports
const viewportId1 = 'CT_AXIAL';
const viewportId2 = 'CT_VOLUME_AXIAL';
const viewportId3 = 'CT_VOLUME_SAG';

const viewportInputArray = [
{
viewportId: viewportId1,
Expand Down Expand Up @@ -298,20 +337,22 @@ async function run() {
await surfaces.forEach((surface) => {
const segmentationId = surface.closedSurface.id;

// // Add the segmentation representation to the toolgroup
segmentation.addSegmentationRepresentations(toolGroupIdStack, [
{
segmentationId,
type: csToolsEnums.SegmentationRepresentations.Surface,
},
]);

segmentation.addSegmentationRepresentations(toolGroupIdVolume, [
{
segmentationId,
type: csToolsEnums.SegmentationRepresentations.Surface,
},
]);
if (volumeFlag) {
// Add the segmentation representation to the toolgroup
segmentation.addSegmentationRepresentations(toolGroupIdStack, [
{
segmentationId,
type: csToolsEnums.SegmentationRepresentations.Surface,
},
]);
} else {
segmentation.addSegmentationRepresentations(toolGroupIdVolume, [
{
segmentationId,
type: csToolsEnums.SegmentationRepresentations.Surface,
},
]);
}
});

// Render the image
Expand Down
2 changes: 2 additions & 0 deletions packages/tools/src/utilities/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import * as viewport from './viewport';
import * as touch from './touch';
import * as dynamicVolume from './dynamicVolume';
import * as polyDataUtils from './polyData/utils';
import { getContoursForSlices } from './polyData/getContoursForSlices';
import * as voi from './voi';

const roundNumber = utilities.roundNumber;
Expand Down Expand Up @@ -91,6 +92,7 @@ export {
roundNumber,
pointToString,
polyDataUtils,
getContoursForSlices,
voi,
annotationFrameRange,
};
130 changes: 130 additions & 0 deletions packages/tools/src/utilities/polyData/getContoursForSlices.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import vtkPlane from '@kitware/vtk.js/Common/DataModel/Plane';
import vtkMapper from '@kitware/vtk.js/Rendering/Core/Mapper';
import vtkActor from '@kitware/vtk.js/Rendering/Core/Actor';
import vtkClipClosedSurface from '@kitware/vtk.js/Filters/General/ClipClosedSurface';
import vtkPolyData from '@kitware/vtk.js/Common/DataModel/PolyData';
import vtkCellArray from '@kitware/vtk.js/Common/Core/CellArray';
import {
VolumeViewport,
VolumeViewport3D,
CONSTANTS,
} from '@cornerstonejs/core';
import { jumpToSlice } from '../viewport';
import { polyDataUtils, viewport } from '..';

/**
* Create a surface actor with vtkClipClosedSurface filter
* @param points
* @param polys
* @param viewport
* @returns
*/
function createSurfaceActor(points, polys, viewport) {
if (viewport instanceof VolumeViewport3D) {
throw new Error('Invalid viewport type');
}

const polyData = vtkPolyData.newInstance();
polyData.getPoints().setData(points, 3);

const triangles = vtkCellArray.newInstance({
values: Float32Array.from(polys),
});
polyData.setPolys(triangles);

const mapper = vtkMapper.newInstance({});
const clippingFilter = vtkClipClosedSurface.newInstance({
clippingPlanes: [],
activePlaneId: 2,
passPointData: false,
});
clippingFilter.setInputData(polyData);
clippingFilter.setGenerateOutline(true);
clippingFilter.setGenerateFaces(false);
clippingFilter.update();
const filteredData = clippingFilter.getOutputData();
mapper.setInputData(filteredData);

const actor = vtkActor.newInstance();
actor.setMapper(mapper);

return { clippingFilter, actor };
}

/**
* Create clipping planes using viewport current position
* @param viewport
* @returns
*/
function createClippingPlanesFromViewportPosition(viewport) {
const vtkPlanes = [vtkPlane.newInstance(), vtkPlane.newInstance()];
const slabThickness = CONSTANTS.RENDERING_DEFAULTS.MINIMUM_SLAB_THICKNESS;
const { viewPlaneNormal, focalPoint } = viewport.getCamera();

viewport.setOrientationOfClippingPlanes(
vtkPlanes,
slabThickness,
viewPlaneNormal,
focalPoint
);
return vtkPlanes;
}

/**
* Extract a contour from a surface
*
* @remarks
* This function will use a mapper from a viewport to retrieve its clipping planes
* and use them to cut the surface and extract its resulting contours
* @param mapper
* @param clippingFilter
* @returns
*/
export function extractContourFromSurface(viewport, mapper, clippingFilter) {
const vtkPlanes = createClippingPlanesFromViewportPosition(viewport);

clippingFilter.setClippingPlanes(vtkPlanes);
try {
clippingFilter.update();
const polyData = clippingFilter.getOutputData();
return polyDataUtils.getPolyDataPoints(polyData);
} catch (e) {
console.error('Error clipping surface', e);
}
}

/**
* Creates an array of contours, one for each slice, from a surface
*
* @remarks
* This function creates a surface actor and use a given viewport to iterate
* through each slice and use its modified clipping planes to cut a surface
* and extract its contours
*
* @param points
* @param polys
* @param viewport
* @returns
*/
export async function getContoursForSlices({ points, polys }, viewport) {
const mapper = viewport.getDefaultActor()?.actor?.getMapper();
if (!mapper) {
return;
}
const { clippingFilter } = createSurfaceActor(points, polys, viewport);
const numberOfSlices = viewport.getImageIds().length;
const isVolumeViewport = viewport instanceof VolumeViewport;
const contours = [];
for (let i = 0; i < numberOfSlices; i++) {
const index = isVolumeViewport ? numberOfSlices - i - 1 : i;
await jumpToSlice(viewport.element, {
imageIndex: index,
debounceLoading: false,
});
const contour = extractContourFromSurface(viewport, mapper, clippingFilter);
if (contour) {
contours.push(contour);
}
}
return contours;
}

0 comments on commit e4a0277

Please sign in to comment.