Skip to content

Commit

Permalink
Created a SmartScrollbar component to show cached slices in each disp…
Browse files Browse the repository at this point in the history
…layset and added an extra feature to prevent scrolling to uncached slices.
  • Loading branch information
Adithyan-Dinesh-Trenser committed Aug 20, 2024
1 parent 83f3bf4 commit d781297
Show file tree
Hide file tree
Showing 20 changed files with 357 additions and 13 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useEffect, useState } from 'react';
import { SmartScrollbar } from '@ohif/ui';

import ViewportImageScrollbar from './ViewportImageScrollbar';
import CustomizableViewportOverlay from './CustomizableViewportOverlay';
Expand Down Expand Up @@ -45,7 +46,7 @@ function CornerstoneOverlays(props: withAppTypes) {

return (
<div className="noselect">
<ViewportImageScrollbar
<SmartScrollbar
viewportId={viewportId}
viewportData={viewportData}
element={element}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ line, it will be truncated with ellipsis in the end.

.viewport-overlay.right-viewport-scrollbar {
text-align: right;
right: 1.7rem;
}
.viewport-overlay.right-viewport-scrollbar .flex.flex-row {
justify-content: flex-end;
Expand Down
13 changes: 12 additions & 1 deletion extensions/cornerstone/src/commandsModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import toggleImageSliceSync from './utils/imageSliceSync/toggleImageSliceSync';
import { getFirstAnnotationSelected } from './utils/measurementServiceMappings/utils/selection';
import getActiveViewportEnabledElement from './utils/getActiveViewportEnabledElement';
import toggleVOISliceSync from './utils/toggleVOISliceSync';
import shouldPreventScroll from './utils/shouldPreventScroll';

const toggleSyncFunctions = {
imageSlice: toggleImageSliceSync,
Expand Down Expand Up @@ -556,7 +557,7 @@ function commandsModule({
const options = { imageIndex: jumpIndex };
cstUtils.jumpToSlice(viewport.element, options);
},
scroll: ({ direction }) => {
scroll: ({ direction, isSmartScrolling = false }) => {
const enabledElement = _getActiveViewportEnabledElement();

if (!enabledElement) {
Expand All @@ -566,6 +567,16 @@ function commandsModule({
const { viewport } = enabledElement;
const options = { delta: direction };

if (
shouldPreventScroll(
!isSmartScrolling,
viewport.getCurrentImageIdIndex() + direction,
servicesManager
)
) {
return;
}

cstUtils.scroll(viewport, options);
},
setViewportColormap: ({
Expand Down
3 changes: 3 additions & 0 deletions extensions/cornerstone/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import getSOPInstanceAttributes from './utils/measurementServiceMappings/utils/g
import { findNearbyToolData } from './utils/findNearbyToolData';
import { createFrameViewSynchronizer } from './synchronizers/frameViewSynchronizer';
import { getSopClassHandlerModule } from './getSopClassHandlerModule';
import shouldPreventScroll from './utils/shouldPreventScroll';

const { helpers: volumeLoaderHelpers } = csStreamingImageVolumeLoader;
const { getDynamicVolumeInfo } = volumeLoaderHelpers ?? {};
Expand Down Expand Up @@ -209,6 +210,8 @@ const cornerstoneExtension: Types.Extensions.Extension = {
exports: {
toolNames,
Enums: cs3DToolsEnums,
shouldPreventScroll: (keyPressed, imageIdIndex) =>
shouldPreventScroll(keyPressed, imageIdIndex, servicesManager),
},
},
{
Expand Down
6 changes: 6 additions & 0 deletions extensions/cornerstone/src/initCornerstoneTools.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ import {

import CalibrationLineTool from './tools/CalibrationLineTool';
import ImageOverlayViewerTool from './tools/ImageOverlayViewerTool';
import SmartStackScrollMouseWheelTool from './tools/SmartStackScrollMouseWheelTool';
import SmartStackScrollTool from './tools/SmartStackScrollTool';

export default function initCornerstoneTools(configuration = {}) {
CrosshairsTool.isAnnotation = false;
Expand Down Expand Up @@ -87,6 +89,8 @@ export default function initCornerstoneTools(configuration = {}) {
addTool(OrientationMarkerTool);
addTool(WindowLevelRegionTool);
addTool(PlanarFreehandContourSegmentationTool);
addTool(SmartStackScrollMouseWheelTool);
addTool(SmartStackScrollTool);

// Modify annotation tools to use dashed lines on SR
const annotationStyle = {
Expand Down Expand Up @@ -142,6 +146,8 @@ const toolNames = {
OrientationMarker: OrientationMarkerTool.toolName,
WindowLevelRegion: WindowLevelRegionTool.toolName,
PlanarFreehandContourSegmentation: PlanarFreehandContourSegmentationTool.toolName,
SmartStackScrollMouseWheel: SmartStackScrollMouseWheelTool.toolName,
SmartStackScroll: SmartStackScrollTool.toolName,
};

export { toolNames };
Original file line number Diff line number Diff line change
Expand Up @@ -252,8 +252,8 @@ export default class ToolGroupService {
}

if (passive) {
passive.forEach(({ toolName }) => {
toolGroup.setToolPassive(toolName);
passive.forEach(({ toolName, bindings }) => {
toolGroup.setToolPassive(toolName, { bindings });
});
}

Expand Down
29 changes: 29 additions & 0 deletions extensions/cornerstone/src/tools/SmartStackScrollMouseWheelTool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { getEnabledElement } from '@cornerstonejs/core';
import { StackScrollMouseWheelTool, Types } from '@cornerstonejs/tools';

class SmartStackScrollMouseWheelTool extends StackScrollMouseWheelTool {
parentMouseWheelCallback: (evt: Types.EventTypes.MouseWheelEventType) => void;

constructor(toolProps, defaultToolProps) {
super(toolProps, defaultToolProps);
this.parentMouseWheelCallback = this.mouseWheelCallback;
this.mouseWheelCallback = this.smartMouseWheelCallback;
}

smartMouseWheelCallback(evt: Types.EventTypes.MouseWheelEventType): void {
const { wheel, element } = evt.detail;
const { direction } = wheel;
const { invert, shouldPreventScroll } = this.configuration;
const { viewport } = getEnabledElement(element);
const delta = direction * (invert ? -1 : 1);

if (shouldPreventScroll(evt.detail.event.ctrlKey, viewport.getCurrentImageIdIndex() + delta)) {
return;
}

this.parentMouseWheelCallback(evt);
}
}

SmartStackScrollMouseWheelTool.toolName = 'SmartStackScrollMouseWheel';
export default SmartStackScrollMouseWheelTool;
32 changes: 32 additions & 0 deletions extensions/cornerstone/src/tools/SmartStackScrollTool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { getEnabledElementByIds } from '@cornerstonejs/core';
import { StackScrollTool, Types } from '@cornerstonejs/tools';

class SmartStackScrollTool extends StackScrollTool {
parentDragCallback: (evt: Types.EventTypes.InteractionEventType) => void;

constructor(toolProps, defaultToolProps) {
super(toolProps, defaultToolProps);
this.parentDragCallback = this._dragCallback;
this._dragCallback = this._smartDragCallback;
}

_smartDragCallback(evt: Types.EventTypes.InteractionEventType) {
const { deltaPoints, viewportId, renderingEngineId } = evt.detail;
const { viewport } = getEnabledElementByIds(viewportId, renderingEngineId);
const { invert, shouldPreventScroll } = this.configuration;
const deltaPointY = deltaPoints.canvas[1];
const pixelsPerImage = this._getPixelPerImage(viewport);
const deltaY = deltaPointY + this.deltaY;
const imageIdIndexOffset = Math.round(deltaY / pixelsPerImage);
const delta = invert ? -imageIdIndexOffset : imageIdIndexOffset;

if (shouldPreventScroll(evt.detail.event.ctrlKey, viewport.getCurrentImageIdIndex() + delta)) {
return;
}

return this.parentDragCallback(evt);
}
}

SmartStackScrollTool.toolName = 'SmartStackScroll';
export default SmartStackScrollTool;
18 changes: 18 additions & 0 deletions extensions/cornerstone/src/utils/shouldPreventScroll.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export default function shouldPreventScroll(
keyPressed: boolean,
imageIdIndex: number,
servicesManager
): boolean {
const { stateSyncService, viewportGridService } = servicesManager.services;
const { cachedSlicesPerSeries } = stateSyncService.getState();
const { activeViewportId, viewports } = viewportGridService.getState();
const cachedSlices = cachedSlicesPerSeries[
viewports.get(activeViewportId).displaySetInstanceUIDs[0]
] as number[];

if (!cachedSlices) {
return false;
}

return !keyPressed && !cachedSlices.includes(imageIdIndex);
}
3 changes: 3 additions & 0 deletions extensions/default/src/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ export default function init({
// afterwards.
stateSyncService.register('viewportsByPosition', { clearOnModeExit: true });

// Stores the cached frames of each series so that we can prevent scrolling to a slice that is not cached
stateSyncService.register('cachedSlicesPerSeries', { clearOnModeExit: true });

// Adds extra custom attributes for use by hanging protocols
registerHangingProtocolAttributes({ servicesManager });

Expand Down
16 changes: 13 additions & 3 deletions modes/longitudinal/src/initToolGroups.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ function initDefaultToolGroup(
'@ohif/extension-cornerstone.utilityModule.tools'
);

const { toolNames, Enums } = utilityModule.exports;
const { toolNames, Enums, shouldPreventScroll } = utilityModule.exports;

const tools = {
active: [
Expand All @@ -37,7 +37,11 @@ function initDefaultToolGroup(
toolName: toolNames.Zoom,
bindings: [{ mouseButton: Enums.MouseBindings.Secondary }],
},
{ toolName: toolNames.StackScrollMouseWheel, bindings: [] },
{
toolName: toolNames.SmartStackScrollMouseWheel,
bindings: [],
configuration: { shouldPreventScroll },
},
],
passive: [
{ toolName: toolNames.Length },
Expand Down Expand Up @@ -71,7 +75,13 @@ function initDefaultToolGroup(
{ toolName: toolNames.EllipticalROI },
{ toolName: toolNames.CircleROI },
{ toolName: toolNames.RectangleROI },
{ toolName: toolNames.StackScroll },
{
toolName: toolNames.SmartStackScroll,
bindings: [
{ mouseButton: Enums.MouseBindings.Primary, modifierKey: Enums.KeyboardBindings.Ctrl },
],
configuration: { shouldPreventScroll },
},
{ toolName: toolNames.Angle },
{ toolName: toolNames.CobbAngle },
{ toolName: toolNames.Magnify },
Expand Down
2 changes: 1 addition & 1 deletion modes/longitudinal/src/moreTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ const moreTools = [
evaluate: 'evaluate.cornerstoneTool.toggle',
}),
createButton({
id: 'StackScroll',
id: 'SmartStackScroll',
icon: 'tool-stack-scroll',
label: 'Stack Scroll',
tooltip: 'Stack Scroll',
Expand Down
16 changes: 13 additions & 3 deletions modes/segmentation/src/initToolGroups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,17 @@ const colorsByOrientation = {
};

function createTools(utilityModule) {
const { toolNames, Enums } = utilityModule.exports;
const { toolNames, Enums, shouldPreventScroll } = utilityModule.exports;
return {
active: [
{ toolName: toolNames.WindowLevel, bindings: [{ mouseButton: Enums.MouseBindings.Primary }] },
{ toolName: toolNames.Pan, bindings: [{ mouseButton: Enums.MouseBindings.Auxiliary }] },
{ toolName: toolNames.Zoom, bindings: [{ mouseButton: Enums.MouseBindings.Secondary }] },
{ toolName: toolNames.StackScrollMouseWheel, bindings: [] },
{
toolName: toolNames.SmartStackScrollMouseWheel,
bindings: [],
configuration: { shouldPreventScroll },
},
],
passive: [
{
Expand Down Expand Up @@ -84,7 +88,13 @@ function createTools(utilityModule) {
{ toolName: toolNames.CircleScissors },
{ toolName: toolNames.RectangleScissors },
{ toolName: toolNames.SphereScissors },
{ toolName: toolNames.StackScroll },
{
toolName: toolNames.SmartStackScroll,
bindings: [
{ mouseButton: Enums.MouseBindings.Primary, modifierKey: Enums.KeyboardBindings.Ctrl },
],
configuration: { shouldPreventScroll },
},
{ toolName: toolNames.Magnify },
{ toolName: toolNames.SegmentationDisplay },
{ toolName: toolNames.WindowLevelRegion },
Expand Down
2 changes: 1 addition & 1 deletion modes/segmentation/src/toolbarButtons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ const toolbarButtons: Button[] = [
evaluate: 'evaluate.cornerstoneTool.toggle',
}),
createButton({
id: 'StackScroll',
id: 'SmartStackScroll',
icon: 'tool-stack-scroll',
label: 'Stack Scroll',
tooltip: 'Stack Scroll',
Expand Down
16 changes: 15 additions & 1 deletion platform/core/src/defaults/hotkeyBindings.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,26 @@ const bindings = [
{
commandName: 'nextImage',
label: 'Next Image',
keys: ['down'],
keys: ['ctrl+down'],
isEditable: true,
},
{
commandName: 'previousImage',
label: 'Previous Image',
keys: ['ctrl+up'],
isEditable: true,
},
{
commandName: 'nextImage',
commandOptions: { isSmartScrolling: true },
label: 'Smart Next Image',
keys: ['down'],
isEditable: true,
},
{
commandName: 'previousImage',
commandOptions: { isSmartScrolling: true },
label: 'Smart Previous Image',
keys: ['up'],
isEditable: true,
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.scroll {
height: calc(100% - 30px);
padding: 5px;
padding-left: 0;
position: absolute;
right: 0;
top: 30px;
Expand Down
Loading

0 comments on commit d781297

Please sign in to comment.