Skip to content

Commit

Permalink
fix(scroll): take into account the slab thickness for scrolling (#849)
Browse files Browse the repository at this point in the history
* fix(scroll): take into account the slab thickness for scrolling

* apply review comments

* apply review comments
  • Loading branch information
sedghi authored Oct 27, 2023
1 parent df9b69e commit 8015160
Show file tree
Hide file tree
Showing 7 changed files with 325 additions and 16 deletions.
2 changes: 2 additions & 0 deletions common/reviews/api/core.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ export abstract class BaseVolumeViewport extends Viewport implements IVolumeView
// (undocumented)
static get useCustomRenderingPipeline(): boolean;
// (undocumented)
protected viewportProperties: VolumeViewportProperties;
// (undocumented)
worldToCanvas: (worldPos: Point3) => Point2;
}

Expand Down
7 changes: 2 additions & 5 deletions packages/core/src/RenderingEngine/BaseVolumeViewport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import type {
Point2,
Point3,
VOIRange,
ViewportProperties,
VolumeViewportProperties,
} from '../types';
import { VoiModifiedEventDetail } from '../types/EventTypes';
Expand Down Expand Up @@ -79,7 +78,7 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {
VolumeViewportProperties
>();

private viewportProperties: VolumeViewportProperties = {};
protected viewportProperties: VolumeViewportProperties = {};

constructor(props: ViewportInput) {
super(props);
Expand Down Expand Up @@ -606,9 +605,7 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport {

if (properties.slabThickness !== undefined) {
this.setSlabThickness(properties.slabThickness);
//We need to set the current slabthickness here since setSlabThickness is define in VolumeViewport
// this.currentViewportProperties.get(volumeId).slabThickness =
// properties.slabThickness;
//We need to set the current slabThickness here since setSlabThickness is define in VolumeViewport
this.viewportProperties.slabThickness = properties.slabThickness;
}

Expand Down
8 changes: 8 additions & 0 deletions packages/core/src/RenderingEngine/VolumeViewport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ class VolumeViewport extends BaseVolumeViewport {
const currentCamera = this.getCamera();
this.updateClippingPlanesForActors(currentCamera);
this.triggerCameraModifiedEventIfNecessary(currentCamera, currentCamera);
this.viewportProperties.slabThickness = slabThickness;
}

/**
Expand Down Expand Up @@ -387,6 +388,13 @@ class VolumeViewport extends BaseVolumeViewport {
throw new Error(`No actor found for the given volumeId: ${volumeId}`);
}

// if a custom slabThickness was set, we need to reset it
if (volumeActor.slabThickness) {
volumeActor.slabThickness = RENDERING_DEFAULTS.MINIMUM_SLAB_THICKNESS;
this.viewportProperties.slabThickness = undefined;
this.updateClippingPlanesForActors(this.getCamera());
}

const imageVolume = cache.getVolume(volumeActor.uid);
if (!imageVolume) {
throw new Error(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import cache from '../cache/cache';
import { EPSILON } from '../constants';
// import type { VolumeViewport } from '../RenderingEngine'
import { ICamera, IImageVolume, IVolumeViewport } from '../types';
import { ICamera, IImageVolume, IVolumeViewport, Point3 } from '../types';
import getSpacingInNormalDirection from './getSpacingInNormalDirection';
import { getVolumeLoaderSchemes } from '../loaders/volumeLoader';

Expand Down Expand Up @@ -71,9 +71,11 @@ export default function getTargetVolumeAndSpacingInNormalDir(

const imageVolume = imageVolumes[imageVolumeIndex];
const { uid: actorUID } = volumeActors[imageVolumeIndex];
const spacingInNormalDirection = getSpacingInNormalDirection(

const spacingInNormalDirection = getSpacingInNormal(
imageVolume,
viewPlaneNormal
viewPlaneNormal,
viewport
);

return { imageVolume, spacingInNormalDirection, actorUID };
Expand Down Expand Up @@ -104,9 +106,10 @@ export default function getTargetVolumeAndSpacingInNormalDir(
continue;
}

const spacingInNormalDirection = getSpacingInNormalDirection(
const spacingInNormalDirection = getSpacingInNormal(
imageVolume,
viewPlaneNormal
viewPlaneNormal,
viewport
);

// Allow for EPSILON part larger requirement to prefer earlier volumes
Expand All @@ -124,3 +127,20 @@ export default function getTargetVolumeAndSpacingInNormalDir(

return smallest;
}

function getSpacingInNormal(
imageVolume: IImageVolume,
viewPlaneNormal: Point3,
viewport: IVolumeViewport
): number {
const { slabThickness } = viewport.getProperties();
let spacingInNormalDirection = slabThickness;
if (!slabThickness) {
spacingInNormalDirection = getSpacingInNormalDirection(
imageVolume,
viewPlaneNormal
);
}

return spacingInNormalDirection;
}
272 changes: 272 additions & 0 deletions packages/tools/examples/volumeSlabScroll/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
import {
RenderingEngine,
Types,
Enums,
setVolumesForViewports,
volumeLoader,
} from '@cornerstonejs/core';
import {
initDemo,
createImageIdsAndCacheMetaData,
setTitleAndDescription,
addButtonToToolbar,
} from '../../../../utils/demo/helpers';
import * as cornerstoneTools from '@cornerstonejs/tools';
import addDropDownToToolbar from '../../../../utils/demo/helpers/addDropdownToToolbar';

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

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

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

// Define a unique id for the volume
const volumeName = 'CT_VOLUME_ID'; // Id of the volume less loader prefix
const volumeLoaderScheme = 'cornerstoneStreamingImageVolume'; // Loader id which defines which volume loader to use
const volumeId = `${volumeLoaderScheme}:${volumeName}`; // VolumeId with loader id + volume id

// ======== Set up page ======== //
setTitleAndDescription(
'Volume Slab Scroll',
'Here we demonstrate how you can programmatically change the slab thickness of volume for rendering and view them in 3D.'
);

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

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

const element1 = document.createElement('div');
const element2 = document.createElement('div');
const element3 = document.createElement('div');
element1.oncontextmenu = () => false;
element2.oncontextmenu = () => false;
element3.oncontextmenu = () => false;

element1.style.width = size;
element1.style.height = size;
element2.style.width = size;
element2.style.height = size;
element3.style.width = size;
element3.style.height = size;

viewportGrid.appendChild(element1);
viewportGrid.appendChild(element2);
viewportGrid.appendChild(element3);

content.appendChild(viewportGrid);

const instructions = document.createElement('p');
instructions.innerText =
'Choose the level of thickness you want to view the volume in 3D.';

content.append(instructions);
// ============================= //

let renderingEngine;

const viewportIds = ['CT_AXIAL', 'CT_SAGITTAL', 'CT_OBLIQUE'];
let activeViewportId = viewportIds[0];
let targetSlabThickness = 1;
let toolGroup;

addDropDownToToolbar({
id: 'viewportIdSelector',
options: {
defaultValue: activeViewportId,
values: viewportIds,
},
labelText: 'Active Viewport to Change Slab Thickness',
onSelectedValueChange: (value) => {
activeViewportId = value as string;
toolGroup.setToolDisabled(ReferenceLines.toolName);
toolGroup.setToolConfiguration(ReferenceLines.toolName, {
sourceViewportId: activeViewportId,
});
toolGroup.setToolEnabled(ReferenceLines.toolName);
renderingEngine.render();
},
});

/**
* - add button to change slab thickness dropdown
* - test on the other orientations
*/

addDropDownToToolbar({
id: 'slabThickness',
options: {
defaultValue: targetSlabThickness,
values: [1, 2.5, 3, 4.5, 5, 20],
},
labelText: 'Slab Thickness',
onSelectedValueChange: (value) => {
targetSlabThickness = value as number;
},
});

addButtonToToolbar({
id: 'slabChange',
title: 'Apply',
onClick: () => {
const viewport = renderingEngine.getViewport(activeViewportId);

viewport.setProperties({ slabThickness: targetSlabThickness });

// Todo: i think we should move this to set properties as well
viewport.setBlendMode(Enums.BlendModes.AVERAGE_INTENSITY_BLEND);
viewport.render();
},
});

addButtonToToolbar({
title: 'Reset',
onClick: () => {
const viewport = renderingEngine.getViewport(activeViewportId);

viewport.resetProperties();
viewport.render();
},
});

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

const toolGroupId = 'STACK_TOOL_GROUP_ID';

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

// Define a tool group, which defines how mouse events map to tool commands for
// Any viewport using the group
toolGroup = ToolGroupManager.createToolGroup(toolGroupId);

// Add the tools to the tool group and specify which volume they are pointing at
toolGroup.addTool(WindowLevelTool.toolName, { volumeId });
toolGroup.addTool(ReferenceLines.toolName, { volumeId });
toolGroup.addTool(PanTool.toolName, { volumeId });
toolGroup.addTool(ZoomTool.toolName, { volumeId });
toolGroup.addTool(StackScrollMouseWheelTool.toolName);

// Set the initial state of the tools, here we set one tool active on left click.
// This means left click will draw that tool.
toolGroup.setToolActive(WindowLevelTool.toolName, {
bindings: [
{
mouseButton: MouseBindings.Primary, // Left Click
},
],
});
toolGroup.setToolActive(PanTool.toolName, {
bindings: [
{
mouseButton: MouseBindings.Auxiliary, // Left Click
},
],
});

toolGroup.setToolActive(ZoomTool.toolName, {
bindings: [
{
mouseButton: MouseBindings.Secondary, // Right Click
},
],
});
toolGroup.setToolEnabled(ReferenceLines.toolName);
toolGroup.setToolConfiguration(ReferenceLines.toolName, {
sourceViewportId: 'CT_AXIAL',
});

// As the Stack Scroll mouse wheel is a tool using the `mouseWheelCallback`
// hook instead of mouse buttons, it does not need to assign any mouse button.
toolGroup.setToolActive(StackScrollMouseWheelTool.toolName);

// Get Cornerstone imageIds and fetch metadata into RAM
const imageIds = await createImageIdsAndCacheMetaData({
StudyInstanceUID:
'1.3.6.1.4.1.14519.5.2.1.7009.2403.334240657131972136850343327463',
SeriesInstanceUID:
'1.3.6.1.4.1.14519.5.2.1.7009.2403.226151125820845824875394858561',
wadoRsRoot: 'https://d3t6nz73ql33tx.cloudfront.net/dicomweb',
});

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

// Create the viewports

const viewportInputArray = [
{
viewportId: viewportIds[0],
type: ViewportType.ORTHOGRAPHIC,
element: element1,
defaultOptions: {
orientation: Enums.OrientationAxis.AXIAL,
background: <Types.Point3>[0.2, 0, 0.2],
},
},
{
viewportId: viewportIds[1],
type: ViewportType.ORTHOGRAPHIC,
element: element2,
defaultOptions: {
orientation: Enums.OrientationAxis.SAGITTAL,
background: <Types.Point3>[0.2, 0, 0.2],
},
},
{
viewportId: viewportIds[2],
type: ViewportType.ORTHOGRAPHIC,
element: element3,
defaultOptions: {
orientation: Enums.OrientationAxis.CORONAL,
background: <Types.Point3>[0.2, 0, 0.2],
},
},
];

renderingEngine.setViewports(viewportInputArray);

// Set the tool group on the viewports
viewportIds.forEach((viewportId) =>
toolGroup.addViewport(viewportId, renderingEngineId)
);

// Define a volume in memory
const volume = await volumeLoader.createAndCacheVolume(volumeId, {
imageIds,
});

// Set the volume to load
volume.load();

setVolumesForViewports(renderingEngine, [{ volumeId }], viewportIds);

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

run();
4 changes: 4 additions & 0 deletions utils/ExampleRunner/example-info.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@
"stackProperties": {
"name": "Stack viewport default properties",
"description": "Demonstrates how you can set per image properties for a stack viewport that acts as default values for that specific image"
},
"volumeSlabScroll": {
"name": "Volume Slab Scroll",
"description": "Demonstrates how to use the slab scroll tool to scroll through a volume"
}
},
"tools-basic": {
Expand Down
Loading

0 comments on commit 8015160

Please sign in to comment.