diff --git a/common/reviews/api/tools.api.md b/common/reviews/api/tools.api.md index 8b669db243..1fbd44cc99 100644 --- a/common/reviews/api/tools.api.md +++ b/common/reviews/api/tools.api.md @@ -4678,6 +4678,8 @@ export class StackScrollMouseWheelTool extends BaseTool { export class StackScrollTool extends BaseTool { constructor(toolProps?: PublicToolProps, defaultToolProps?: ToolProps); // (undocumented) + deltaX: number; + // (undocumented) deltaY: number; // (undocumented) _dragCallback(evt: EventTypes_2.InteractionEventType): void; @@ -4691,6 +4693,8 @@ export class StackScrollTool extends BaseTool { static toolName: any; // (undocumented) touchDragCallback(evt: EventTypes_2.InteractionEventType): void; + // (undocumented) + _triggerMIP(viewport: any, delta: any, invert: any): void; } // @public diff --git a/packages/tools/examples/volumeViewportSynchronization/index.ts b/packages/tools/examples/volumeViewportSynchronization/index.ts index 45c3a0a421..fcdee8521a 100644 --- a/packages/tools/examples/volumeViewportSynchronization/index.ts +++ b/packages/tools/examples/volumeViewportSynchronization/index.ts @@ -10,6 +10,7 @@ import { createImageIdsAndCacheMetaData, setTitleAndDescription, addToggleButtonToToolbar, + addDropdownToToolbar, } from '../../../../utils/demo/helpers'; import * as cornerstoneTools from '@cornerstonejs/tools'; @@ -24,6 +25,7 @@ const { ZoomTool, ToolGroupManager, StackScrollMouseWheelTool, + StackScrollTool, Enums: csToolsEnums, synchronizers, SynchronizerManager, @@ -97,6 +99,33 @@ Toggle the controls to add viewports to the synchronization groups. content.append(instructions); // ============================= // +const toolGroupId = 'TOOL_GROUP_ID'; +const leftClickTools = [WindowLevelTool.toolName, StackScrollTool.toolName]; +const defaultLeftClickTool = leftClickTools[0]; +let currentLeftClickTool = leftClickTools[0]; +addDropdownToToolbar({ + options: { + values: leftClickTools, + defaultValue: defaultLeftClickTool, + }, + onSelectedValueChange: (selectedValue) => { + console.log('>>>>> selectedValue :: ', selectedValue); + const toolGroup = ToolGroupManager.getToolGroup(toolGroupId); + console.log('>>>>> toolGroup :: ', toolGroup); + + toolGroup.setToolPassive(currentLeftClickTool); + + toolGroup.setToolActive(selectedValue, { + bindings: [ + { + mouseButton: MouseBindings.Primary, // Left Click + }, + ], + }); + + currentLeftClickTool = selectedValue; + }, +}); const SynchronizerButtonInfo = [ { viewportLabel: 'A', viewportId: viewportIds[0] }, @@ -151,12 +180,11 @@ async function run() { // Init Cornerstone and related libraries await initDemo(); - const toolGroupId = 'TOOL_GROUP_ID'; - // Add tools to Cornerstone3D cornerstoneTools.addTool(PanTool); cornerstoneTools.addTool(WindowLevelTool); cornerstoneTools.addTool(StackScrollMouseWheelTool); + cornerstoneTools.addTool(StackScrollTool); cornerstoneTools.addTool(ZoomTool); // Define a tool group, which defines how mouse events map to tool commands for @@ -164,10 +192,20 @@ async function run() { const toolGroup = ToolGroupManager.createToolGroup(toolGroupId); // Add tools to the tool group - toolGroup.addTool(WindowLevelTool.toolName, { volumeId }); + toolGroup.addTool(WindowLevelTool.toolName); toolGroup.addTool(PanTool.toolName); toolGroup.addTool(ZoomTool.toolName); toolGroup.addTool(StackScrollMouseWheelTool.toolName); + toolGroup.addTool(StackScrollTool.toolName, { + leftRightMode: false, + mipMode: { + enabled: true, + invert: false, + pixelsPerThickness: 5, + minSlabThickness: 5e-2, + maxSlabThickness: 30, + }, + }); // Set the initial state of the tools, here all tools are active and bound to // Different mouse inputs diff --git a/packages/tools/src/tools/StackScrollTool.ts b/packages/tools/src/tools/StackScrollTool.ts index 93afd056da..9ff38af485 100644 --- a/packages/tools/src/tools/StackScrollTool.ts +++ b/packages/tools/src/tools/StackScrollTool.ts @@ -15,19 +15,30 @@ import { PublicToolProps, ToolProps, EventTypes } from '../types'; class StackScrollTool extends BaseTool { static toolName; deltaY: number; + deltaX: number; constructor( toolProps: PublicToolProps = {}, defaultToolProps: ToolProps = { supportedInteractionTypes: ['Mouse', 'Touch'], configuration: { invert: false, + leftRightMode: false, debounceIfNotLoaded: true, - loop: false + loop: false, + stackScrollEnabled: true, + mipMode: { + enabled: true, + invert: false, + pixelsPerThickness: 5, + minSlabThickness: 5e-2, + maxSlabThickness: 30, + }, }, } ) { super(toolProps, defaultToolProps); this.deltaY = 1; + this.deltaX = 1; } mouseDragCallback(evt: EventTypes.InteractionEventType) { @@ -42,9 +53,16 @@ class StackScrollTool extends BaseTool { const { viewport } = getEnabledElementByIds(viewportId, renderingEngineId); const targetId = this.getTargetId(viewport); - const { debounceIfNotLoaded, invert, loop } = this.configuration; + const { + debounceIfNotLoaded, + invert, + loop, + leftRightMode, + stackScrollEnabled, + } = this.configuration; const deltaPointY = deltaPoints.canvas[1]; + const deltaPointX = deltaPoints.canvas[0]; let volumeId; if (viewport instanceof VolumeViewport) { @@ -53,24 +71,66 @@ class StackScrollTool extends BaseTool { const pixelsPerImage = this._getPixelPerImage(viewport); const deltaY = deltaPointY + this.deltaY; + const deltaX = deltaPointX + this.deltaX; if (!pixelsPerImage) { return; } - if (Math.abs(deltaY) >= pixelsPerImage) { - const imageIdIndexOffset = Math.round(deltaY / pixelsPerImage); + if (stackScrollEnabled && !leftRightMode) { + if (Math.abs(deltaY) >= pixelsPerImage) { + const imageIdIndexOffset = Math.round(deltaY / pixelsPerImage); - scroll(viewport, { - delta: invert ? -imageIdIndexOffset : imageIdIndexOffset, - volumeId, - debounceLoading: debounceIfNotLoaded, - loop: loop - }); + scroll(viewport, { + delta: invert ? -imageIdIndexOffset : imageIdIndexOffset, + volumeId, + debounceLoading: debounceIfNotLoaded, + loop: loop, + }); - this.deltaY = deltaY % pixelsPerImage; - } else { - this.deltaY = deltaY; + this.deltaY = deltaY % pixelsPerImage; + } else { + this.deltaY = deltaY; + } + } + + if (stackScrollEnabled && leftRightMode) { + if (Math.abs(deltaX) >= pixelsPerImage) { + const imageIdIndexOffset = Math.round(deltaX / pixelsPerImage); + + scroll(viewport, { + delta: invert ? -imageIdIndexOffset : imageIdIndexOffset, + volumeId, + debounceLoading: debounceIfNotLoaded, + loop: loop, + }); + + this.deltaX = deltaX % pixelsPerImage; + } else { + this.deltaX = deltaX; + } + } + + const { mipMode } = this.configuration; + if (!mipMode?.enabled) return; + const { pixelsPerThickness, mipModeInvert } = mipMode; + + if (mipMode?.enabled && leftRightMode) { + if (Math.abs(deltaY) >= pixelsPerThickness) { + this._triggerMIP(viewport, deltaY > 0 ? -1 : 1, mipModeInvert); + this.deltaY = deltaY % pixelsPerThickness; + } else { + this.deltaY = deltaY; + } + } + + if (mipMode?.enabled && !leftRightMode) { + if (Math.abs(deltaX) >= pixelsPerThickness) { + this._triggerMIP(viewport, deltaX > 0 ? 1 : -1, mipModeInvert); + this.deltaX = deltaX % pixelsPerThickness; + } else { + this.deltaX = deltaX; + } } } @@ -91,6 +151,28 @@ class StackScrollTool extends BaseTool { return viewport.getImageIds().length; } } + + _triggerMIP(viewport, delta, invert) { + const inversionValue = invert ? -1 : 1; + const { minSlabThickness, maxSlabThickness } = this.configuration.mipMode; + if (viewport instanceof VolumeViewport) { + const slabThickness = Math.min( + maxSlabThickness, + viewport.getSlabThickness() + inversionValue * delta + ); + if (slabThickness <= minSlabThickness) { + viewport.setBlendMode(0); + viewport.setSlabThickness(minSlabThickness); + viewport.render(); + } else { + viewport.setBlendMode(1); + viewport.setSlabThickness( + slabThickness >= maxSlabThickness ? maxSlabThickness : slabThickness + ); + viewport.render(); + } + } + } } StackScrollTool.toolName = 'StackScroll';