Skip to content
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) {

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
14 changes: 13 additions & 1 deletion extensions/cornerstone/src/commandsModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import callInputDialog from './utils/callInputDialog';
import toggleStackImageSync from './utils/stackSync/toggleStackImageSync';
import { getFirstAnnotationSelected } from './utils/measurementServiceMappings/utils/selection';
import getActiveViewportEnabledElement from './utils/getActiveViewportEnabledElement';
import shouldPreventScroll from './utils/shouldPreventScroll';
import { CornerstoneServices } from './types';

function commandsModule({
Expand All @@ -35,6 +36,7 @@ function commandsModule({
cornerstoneViewportService,
uiNotificationService,
measurementService,
stateSyncService,
} = servicesManager.services as CornerstoneServices;

const { measurementServiceSource } = this;
Expand Down Expand Up @@ -541,7 +543,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 @@ -551,6 +553,16 @@ function commandsModule({
const { viewport } = enabledElement;
const options = { delta: direction };

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

cstUtils.scroll(viewport, options);
},
setViewportColormap: ({ viewportId, displaySetInstanceUID, colormap, immediate = false }) => {
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 @@ -30,6 +30,7 @@ import * as csWADOImageLoader from './initWADOImageLoader.js';
import { measurementMappingUtils } from './utils/measurementServiceMappings';
import type { PublicViewportOptions } from './services/ViewportService/Viewport';
import ImageOverlayViewerTool from './tools/ImageOverlayViewerTool';
import shouldPreventScroll from './utils/shouldPreventScroll';

const Component = React.lazy(() => {
return import(/* webpackPrefetch: true */ './Viewport/OHIFCornerstoneViewport');
Expand Down Expand Up @@ -129,6 +130,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 @@ -32,6 +32,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 @@ -66,6 +68,8 @@ export default function initCornerstoneTools(configuration = {}) {
addTool(RectangleScissorsTool);
addTool(SphereScissorsTool);
addTool(ImageOverlayViewerTool);
addTool(SmartStackScrollMouseWheelTool);
addTool(SmartStackScrollTool);

// Modify annotation tools to use dashed lines on SR
const annotationStyle = {
Expand Down Expand Up @@ -111,6 +115,8 @@ const toolNames = {
RectangleScissors: RectangleScissorsTool.toolName,
SphereScissors: SphereScissorsTool.toolName,
ImageOverlayViewer: ImageOverlayViewerTool.toolName,
SmartStackScrollMouseWheel: SmartStackScrollMouseWheelTool.toolName,
SmartStackScroll: SmartStackScrollTool.toolName,
};

export { toolNames };
Original file line number Diff line number Diff line change
Expand Up @@ -245,8 +245,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 @@ -46,6 +46,9 @@ export default function init({ servicesManager, configuration = {} }): void {
// changes numRows and numCols, the viewports can be remembers and then replaced
// 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 });
}

const handlePETImageMetadata = ({ SeriesInstanceUID, StudyInstanceUID }) => {
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 @@ -17,13 +17,17 @@ const brushStrategies = {
};

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: Object.keys(brushInstanceNames)
.map(brushName => ({
Expand All @@ -37,7 +41,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 },
]),
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 @@ -288,7 +288,7 @@ const toolbarButtons = [
{
commandName: 'setToolActive',
commandOptions: {
toolName: 'StackScroll',
toolName: 'SmartStackScroll',
},
context: 'CORNERSTONE',
},
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: 100%;
padding: 5px;
padding-left: 0;
position: absolute;
right: 0;
top: 0;
Expand Down
Loading