Skip to content

Commit

Permalink
fix(crosshairs): and orientation markers and publish (#856)
Browse files Browse the repository at this point in the history
* fix

* api

* fix crosshairs

* tests
  • Loading branch information
sedghi authored Oct 27, 2023
1 parent 545dab6 commit 9722013
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 49 deletions.
2 changes: 1 addition & 1 deletion common/reviews/api/core.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -2788,7 +2788,7 @@ export class VideoViewport extends Viewport implements IVideoViewport {
// (undocumented)
readonly uid: any;
// (undocumented)
static readonly useCustomRenderingPipeline = true;
static get useCustomRenderingPipeline(): boolean;
// (undocumented)
worldToCanvas: (worldPos: Point3) => Point2;
}
Expand Down
6 changes: 4 additions & 2 deletions packages/core/src/RenderingEngine/VideoViewport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ import { getOrCreateCanvas } from './helpers';
* looking into an internal scene, and an associated target output `canvas`.
*/
class VideoViewport extends Viewport implements IVideoViewport {
public static readonly useCustomRenderingPipeline = true;

// Viewport Data
readonly uid;
readonly renderingEngineId: string;
Expand Down Expand Up @@ -61,6 +59,10 @@ class VideoViewport extends Viewport implements IVideoViewport {
this.resize();
}

public static get useCustomRenderingPipeline() {
return true;
}

private addEventListeners() {
this.canvas.addEventListener(
EVENTS.ELEMENT_DISABLED,
Expand Down
12 changes: 10 additions & 2 deletions packages/core/src/RenderingEngine/Viewport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ import Events from '../enums/Events';
import ViewportStatus from '../enums/ViewportStatus';
import ViewportType from '../enums/ViewportType';
import renderingEngineCache from './renderingEngineCache';
import { triggerEvent, planar, isImageActor, actorIsA } from '../utilities';
import {
triggerEvent,
planar,
isImageActor,
actorIsA,
isEqual,
} from '../utilities';
import hasNaNValues from '../utilities/hasNaNValues';
import { EPSILON, RENDERING_DEFAULTS } from '../constants';
import type {
Expand Down Expand Up @@ -1085,16 +1091,18 @@ class Viewport implements IViewport {

// update clipping range only if focal point changed of a new actor is added
const prevFocalPoint = previousCamera.focalPoint;

if (prevFocalPoint && focalPoint) {
const currentViewPlaneNormal = <Point3>vtkCamera.getViewPlaneNormal();

const deltaCamera = <Point3>[
focalPoint[0] - prevFocalPoint[0],
focalPoint[1] - prevFocalPoint[1],
focalPoint[2] - prevFocalPoint[2],
];

const cameraModifiedOutOfPlane =
Math.abs(vtkMath.dot(deltaCamera, currentViewPlaneNormal)) > EPSILON;
Math.abs(vtkMath.dot(deltaCamera, currentViewPlaneNormal)) > 0;

// only modify the clipping planes if the camera is modified out of plane
// or a new actor is added and we need to update the clipping planes
Expand Down
5 changes: 5 additions & 0 deletions packages/docs/docs/tutorials/basic-video.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ It may require an accept header to force it to be served in MP4 format if it is
It may not support either the fast start encoding or the byte range format, absence of
which will prevent seeking through large videos. Small videos will likely be buffered
entirely, so they can still seek.

For instance you can look at this example in OHIF which uses the rendered endpoint:
`https://d33do7qe4w26qo.cloudfront.net/dicomweb/studies/2.25.96975534054447904995905761963464388233/series/2.25.15054212212536476297201250326674987992/instances/2.25.179478223177027022014772769075050874231/rendered`


:::

## Final code
Expand Down
150 changes: 107 additions & 43 deletions packages/tools/examples/orientationMarker/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,44 @@ import {
Enums,
volumeLoader,
setVolumesForViewports,
CONSTANTS,
utilities,
Types,
} from '@cornerstonejs/core';
import {
initDemo,
createImageIdsAndCacheMetaData,
setCtTransferFunctionForVolumeActor,
setTitleAndDescription,
} from '../../../../utils/demo/helpers';
import * as cornerstoneTools from '@cornerstonejs/tools';
import addDropDownToToolbar from '../../../../utils/demo/helpers/addDropdownToToolbar';
import setPetTransferFunction from '../../../../utils/demo/helpers/setPetTransferFunctionForVolumeActor';

async function getImageStacks() {
// Get Cornerstone imageIds for the source data and fetch metadata into RAM
const wadoRsRoot = 'https://d33do7qe4w26qo.cloudfront.net/dicomweb';
const wadoRsRoot1 = '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',
];
const axialImageIds = await createImageIdsAndCacheMetaData({
const ctImageIds = await createImageIdsAndCacheMetaData({
StudyInstanceUID: studyInstanceUID,
SeriesInstanceUID: seriesInstanceUIDs[0],
wadoRsRoot: wadoRsRoot1,
});

const wadoRsRoot = 'https://d3t6nz73ql33tx.cloudfront.net/dicomweb';
const StudyInstanceUID =
'1.3.6.1.4.1.14519.5.2.1.7009.2403.334240657131972136850343327463';

const ptImageIds = await createImageIdsAndCacheMetaData({
StudyInstanceUID,
SeriesInstanceUID:
'1.3.6.1.4.1.14519.5.2.1.7009.2403.879445243400782656317561081015',
wadoRsRoot,
});

return axialImageIds;
return [ctImageIds, ptImageIds];
}
// This is for debugging purposes
console.warn(
Expand All @@ -44,28 +57,37 @@ const {
TrackballRotateTool,
} = cornerstoneTools;

const ctToolGroupId = 'CT_TOOLGROUP_ID';
const ptToolGroupId = 'PT_TOOLGROUP_ID';
let ctToolGroup;
let ptToolGroup;

addDropDownToToolbar({
options: {
values: Object.keys(OrientationMarkerTool.OVERLAY_MARKER_TYPES),
defaultValue: OrientationMarkerTool.OVERLAY_MARKER_TYPES.AXES,
},
onSelectedValueChange: (value) => {
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId);
toolGroup.setToolConfiguration(OrientationMarkerTool.toolName, {
overlayMarkerType: OrientationMarkerTool.OVERLAY_MARKER_TYPES[value],
});
[ctToolGroup, ptToolGroup].forEach((toolGroup) => {
toolGroup.setToolDisabled(OrientationMarkerTool.toolName);
toolGroup.setToolConfiguration(OrientationMarkerTool.toolName, {
overlayMarkerType: OrientationMarkerTool.OVERLAY_MARKER_TYPES[value],
});

toolGroup.setToolEnabled(OrientationMarkerTool.toolName);
toolGroup.setToolEnabled(OrientationMarkerTool.toolName);
});
},
});

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

// Define a unique id for the volume
const volumeName = 'CT_VOLUME_ID'; // Id of the volume less loader prefix
const ctVolumeName = 'CT_VOLUME_ID'; // Id of the volume less loader prefix
const ptVolumeName = 'PT_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
const ctVolumeId = `${volumeLoaderScheme}:${ctVolumeName}`;
const ptVolumeId = `${volumeLoaderScheme}:${ptVolumeName}`;
const toolGroupId = 'MY_TOOLGROUP_ID';

// ======== Set up page ======== //
Expand Down Expand Up @@ -114,7 +136,8 @@ const renderingEngineId = 'myRenderingEngine';
*/
async function run() {
// Define tool groups to add the segmentation display tool to
const toolGroup = ToolGroupManager.createToolGroup(toolGroupId);
ctToolGroup = ToolGroupManager.createToolGroup(ctToolGroupId);
ptToolGroup = ToolGroupManager.createToolGroup(ptToolGroupId);

// Init Cornerstone and related libraries
await initDemo();
Expand All @@ -123,22 +146,33 @@ async function run() {
cornerstoneTools.addTool(OrientationMarkerTool);
cornerstoneTools.addTool(PanTool);
cornerstoneTools.addTool(ZoomTool);
cornerstoneTools.addTool(TrackballRotateTool);
cornerstoneTools.addTool(VolumeRotateMouseWheelTool);
cornerstoneTools.addTool(TrackballRotateTool);

toolGroup.addTool(OrientationMarkerTool.toolName);
toolGroup.addTool(ZoomTool.toolName);
toolGroup.addTool(PanTool.toolName);
toolGroup.addTool(VolumeRotateMouseWheelTool.toolName);
toolGroup.addTool(TrackballRotateTool.toolName);

toolGroup.setToolActive(TrackballRotateTool.toolName, {
ctToolGroup.addTool(OrientationMarkerTool.toolName);
ctToolGroup.addTool(ZoomTool.toolName);
ctToolGroup.addTool(PanTool.toolName);
ctToolGroup.addTool(TrackballRotateTool.toolName);
ctToolGroup.setToolActive(TrackballRotateTool.toolName, {
bindings: [
{
mouseButton: MouseBindings.Primary, // Left Click
},
],
});
ctToolGroup.setToolActive(ZoomTool.toolName, {
bindings: [
{
mouseButton: MouseBindings.Secondary, // Left Click
},
],
});

ptToolGroup.addTool(OrientationMarkerTool.toolName);
ptToolGroup.addTool(ZoomTool.toolName);
ptToolGroup.addTool(PanTool.toolName);
ptToolGroup.addTool(VolumeRotateMouseWheelTool.toolName);
ptToolGroup.setToolActive(VolumeRotateMouseWheelTool.toolName);

// Instantiate a rendering engine
const renderingEngine = new RenderingEngine(renderingEngineId);
Expand Down Expand Up @@ -167,49 +201,79 @@ async function run() {
element: elements[2],
defaultOptions: {
orientation: Enums.OrientationAxis.CORONAL,
background: [1, 1, 1],
},
},
];

// @ts-ignore
renderingEngine.setViewports(viewportInputArray);

const usedViewportIds = viewportInputArray.map(({ viewportId }) => {
toolGroup.addViewport(viewportId, renderingEngineId);
return viewportId;
});

const imageIds = await getImageStacks();
const [ctImageIds, ptImageIds] = await getImageStacks();

// Define a volume in memory
const volume = await volumeLoader.createAndCacheVolume(volumeId, {
imageIds,
const ctVolume = await volumeLoader.createAndCacheVolume(ctVolumeId, {
imageIds: ctImageIds,
});
volume.load();
const ptVolume = await volumeLoader.createAndCacheVolume(ptVolumeId, {
imageIds: ptImageIds,
});

ctVolume.load();
ptVolume.load();

ctToolGroup.addViewport(viewportIds[0], renderingEngineId);
ctToolGroup.addViewport(viewportIds[1], renderingEngineId);
ptToolGroup.addViewport(viewportIds[2], renderingEngineId);

await setVolumesForViewports(
const ctViewportIds = viewportIds.slice(0, 2);

setVolumesForViewports(
renderingEngine,
[
{
volumeId,
callback: setCtTransferFunctionForVolumeActor,
blendMode: Enums.BlendModes.MAXIMUM_INTENSITY_BLEND,
// Todo: just for test
slabThickness: 100,
volumeId: ctVolumeId,
slabThickness: 300,
},
],
usedViewportIds
);
[...ctViewportIds]
).then(() => {
ctViewportIds.forEach((viewportId) => {
const volumeActor = renderingEngine
.getViewport(viewportId)
.getDefaultActor().actor as Types.VolumeActor;

toolGroup.setToolActive(OrientationMarkerTool.toolName);
toolGroup.setToolActive(VolumeRotateMouseWheelTool.toolName);
toolGroup.setToolActive(ZoomTool.toolName, {
bindings: [
utilities.applyPreset(
volumeActor,
CONSTANTS.VIEWPORT_PRESETS.find((preset) => preset.name === 'CT-Bone')
);

const viewport = renderingEngine.getViewport(viewportId);

viewport.render();
});
});

setVolumesForViewports(
renderingEngine,
[
{
mouseButton: MouseBindings.Secondary, // Left Click
volumeId: ptVolumeId,
callback: setPetTransferFunction,
blendMode: Enums.BlendModes.MAXIMUM_INTENSITY_BLEND,
slabThickness: 300,
},
],
[viewportIds[2]]
).then(() => {
const viewport = renderingEngine.getViewport(viewportIds[2]);

viewport.render();
});

ctToolGroup.setToolActive(OrientationMarkerTool.toolName);
ptToolGroup.setToolActive(OrientationMarkerTool.toolName);

// Render the image
renderingEngine.render();
}
Expand Down
1 change: 0 additions & 1 deletion packages/tools/src/tools/OrientationMarkerTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,6 @@ class OrientationMarkerTool extends BaseTool {
orientationWidget,
actor,
};
renderer.resetCamera();
renderWindow.render();
viewport.getRenderingEngine().render();

Expand Down

0 comments on commit 9722013

Please sign in to comment.