Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SmartScrollbar component to show cached slices #4340

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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';
@@ -45,7 +46,7 @@ function CornerstoneOverlays(props: withAppTypes) {

return (
<div className="noselect">
<ViewportImageScrollbar
<SmartScrollbar
viewportId={viewportId}
viewportData={viewportData}
element={element}
Original file line number Diff line number Diff line change
@@ -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;
13 changes: 12 additions & 1 deletion extensions/cornerstone/src/commandsModule.ts
Original file line number Diff line number Diff line change
@@ -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,
@@ -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) {
@@ -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: ({
3 changes: 3 additions & 0 deletions extensions/cornerstone/src/index.tsx
Original file line number Diff line number Diff line change
@@ -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 ?? {};
@@ -209,6 +210,8 @@ const cornerstoneExtension: Types.Extensions.Extension = {
exports: {
toolNames,
Enums: cs3DToolsEnums,
shouldPreventScroll: (keyPressed, imageIdIndex) =>
shouldPreventScroll(keyPressed, imageIdIndex, servicesManager),
},
},
{
6 changes: 6 additions & 0 deletions extensions/cornerstone/src/initCornerstoneTools.js
Original file line number Diff line number Diff line change
@@ -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;
@@ -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 = {
@@ -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
@@ -252,8 +252,8 @@ export default class ToolGroupService {
}

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

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
@@ -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 });

16 changes: 13 additions & 3 deletions modes/longitudinal/src/initToolGroups.js
Original file line number Diff line number Diff line change
@@ -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: [
@@ -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 },
@@ -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 },
2 changes: 1 addition & 1 deletion modes/longitudinal/src/moreTools.ts
Original file line number Diff line number Diff line change
@@ -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',
16 changes: 13 additions & 3 deletions modes/segmentation/src/initToolGroups.ts
Original file line number Diff line number Diff line change
@@ -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: [
{
@@ -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 },
2 changes: 1 addition & 1 deletion modes/segmentation/src/toolbarButtons.ts
Original file line number Diff line number Diff line change
@@ -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',
16 changes: 15 additions & 1 deletion platform/core/src/defaults/hotkeyBindings.js
Original file line number Diff line number Diff line change
@@ -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,
},
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;
Loading