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
Show file tree
Hide file tree
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';
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