Skip to content

Commit

Permalink
feat: cs3d tools and toolGroups (#20)
Browse files Browse the repository at this point in the history
* add more tools to work with cs3d mode

* fix hotkeys

* add stack manager usage for stach viewports

* add image scrollbar

* wip viewport overlay

* fix toAnnotation schema for tools

* fix the unnecessary size change that triggered resize

* hanging protocol improvement to allow unmatched errors

* study description matching for hanging protocol

* fix the displaysetOptions to work

* fix handle the active tool when a new viewport is added

* fix separate toolGroups for mode

* apply review comments

* apply review comments

* yarn lock
  • Loading branch information
sedghi committed May 16, 2022
1 parent 0ccb6c9 commit e943f60
Show file tree
Hide file tree
Showing 40 changed files with 1,357 additions and 469 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.viewport-wrapper {
width: 100%;
height: 100%; /* MUST have `height` to prevent resize infinite loop */
position: relative;
}

.viewport-element {
width: 100%;
height: 100%;
position: relative;
background-color: black;

/* Prevent the blue outline in Chrome when a viewport is selected */
outline: 0 !important;

/* Prevents the entire page from getting larger
when the magnify tool is near the sides/corners of the page */
overflow: hidden;
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import React, { useEffect, useRef, useCallback } from 'react';
import { utilities } from '@cornerstonejs/tools';
import React, { useEffect, useRef, useCallback, useState } from 'react';
import ReactResizeDetector from 'react-resize-detector';
import { useViewportGrid } from '@ohif/ui';
import { useViewportGrid, ImageScrollbar } from '@ohif/ui';
import OHIF from '@ohif/core';
import { utilities } from '@cornerstonejs/tools';
import { Enums } from '@cornerstonejs/core';

import Cornerstone3DViewportService from '../services/ViewportService/Cornerstone3DViewportService';

import './OHIFCornerstone3DViewport.css';

const { StackManager } = OHIF.utils;

const STACK = 'stack';

function areEqual(prevProps, nextProps) {
if (nextProps.needsRerendering) {
Expand All @@ -18,14 +28,17 @@ function areEqual(prevProps, nextProps) {
const nextDisplaySets = nextProps.displaySets[0];

if (prevDisplaySets && nextDisplaySets) {
return (
const areSameDisplaySetInstanceUIDs =
prevDisplaySets.displaySetInstanceUID ===
nextDisplaySets.displaySetInstanceUID &&
prevDisplaySets.images.length === nextDisplaySets.images.length &&
prevDisplaySets.images.every(
(prevImage, index) =>
prevImage.imageId === nextDisplaySets.images[index].imageId
)
nextDisplaySets.displaySetInstanceUID;
const areSameImageLength =
prevDisplaySets.images.length === nextDisplaySets.images.length;
const areSameImageIds = prevDisplaySets.images.every(
(prevImage, index) =>
prevImage.imageId === nextDisplaySets.images[index].imageId
);
return (
areSameDisplaySetInstanceUIDs && areSameImageLength && areSameImageIds
);
}
return false;
Expand All @@ -43,56 +56,83 @@ const OHIFCornerstoneViewport = React.memo(props => {
servicesManager,
} = props;

const [viewportData, setViewportData] = useState(null);
const [scrollbarIndex, setScrollbarIndex] = useState(0);
const [scrollbarHeight, setScrollbarHeight] = useState('100px');
const [_, viewportGridService] = useViewportGrid();

const elementRef = useRef();
const {
ViewportService,
MeasurementService,
DisplaySetService,
} = servicesManager.services;
const { MeasurementService, DisplaySetService } = servicesManager.services;

// useCallback for scroll bar height calculation
const setImageScrollBarHeight = useCallback(() => {
const scrollbarHeight = `${elementRef.current.clientHeight - 20}px`;
setScrollbarHeight(scrollbarHeight);
}, [elementRef]);

// useCallback for onResize
const onResize = useCallback(
props => {
if (elementRef.current) {
const element = elementRef.current;
ViewportService.resize();
}
},
[elementRef]
);
const onResize = useCallback(() => {
if (elementRef.current) {
Cornerstone3DViewportService.resize();
setImageScrollBarHeight();
}
}, [elementRef]);

// disable the element upon unmounting
useEffect(() => {
// setElementRef(targetRef.current);
ViewportService.enableElement(viewportIndex, elementRef.current);
Cornerstone3DViewportService.enableElement(
viewportIndex,
elementRef.current
);
setImageScrollBarHeight();
return () => {
ViewportService.disableElement(viewportIndex);
Cornerstone3DViewportService.disableElement(viewportIndex);
};
}, []);

// Todo: Use stackManager to handle the stack creation and imageId get, problem
// would be what to do with the volumes which needs different schema for loading: streaming-wadors
// Stack manager shouldn't care about the type of volume. Maybe add a postImageId creation callback?
// Maybe we should need a volumeManager later on.
useEffect(() => {
ViewportService.setViewportDisplaySets(
viewportIndex,
const viewportData = _getViewportData(
dataSource,
displaySets,
viewportOptions.viewportType
);

Cornerstone3DViewportService.setViewportDisplaySets(
viewportIndex,
viewportData,
viewportOptions,
displaySetOptions,
dataSource
displaySetOptions
);

setViewportData(viewportData);
}, [
viewportIndex,
viewportOptions,
displaySetOptions,
displaySets,
dataSource,
ViewportService,
]);

useEffect(() => {
const element = elementRef.current;

const updateIndex = event => {
const { imageId } = event.detail;
// find the index of imageId in the imageIds
const index = viewportData.stack?.imageIds.indexOf(imageId);

if (index !== -1) {
setScrollbarIndex(index);
}
};

element.addEventListener(Enums.Events.STACK_NEW_IMAGE, updateIndex);

return () => {
element.removeEventListener(Enums.Events.STACK_NEW_IMAGE, updateIndex);
};
}, [elementRef, viewportData]);

/**
* There are two scenarios for jump to click
* 1. Current viewports contain the displaySet that the annotation was drawn on
Expand Down Expand Up @@ -125,17 +165,37 @@ const OHIFCornerstoneViewport = React.memo(props => {
return () => {
unsubscribeFromJumpToMeasurementEvents();
};
}, [
displaySets,
elementRef,
viewportIndex,
viewportGridService,
MeasurementService,
DisplaySetService,
]);
}, [displaySets, elementRef, viewportIndex]);

const onImageScrollbarChange = useCallback(
(imageIndex, viewportIndex) => {
const viewportInfo = Cornerstone3DViewportService.getViewportInfoByIndex(
viewportIndex
);

const viewportId = viewportInfo.getViewportId();
const viewport = Cornerstone3DViewportService.getCornerstone3DViewport(
viewportId
);

// if getCurrentImageId is not a method on viewport
if (!viewport.getCurrentImageId) {
throw new Error('cannot use scrollbar for non-stack viewports');
}

// Later scrollThroughStack should return two values the current index
// and the total number of indices (volume it is different)
viewport.setImageIdIndex(imageIndex).then(() => {
// Update scrollbar index
const currentIndex = viewport.getCurrentImageIdIndex();
setScrollbarIndex(currentIndex);
});
},
[viewportIndex, viewportData]
);

return (
<React.Fragment>
<div className="viewport-wrapper">
<ReactResizeDetector
handleWidth
handleHeight
Expand All @@ -152,10 +212,46 @@ const OHIFCornerstoneViewport = React.memo(props => {
onMouseDown={e => e.preventDefault()}
ref={elementRef}
></div>
</React.Fragment>
<ImageScrollbar
onChange={evt => onImageScrollbarChange(evt, viewportIndex)}
max={viewportData ? viewportData.stack?.imageIds?.length - 1 : 0}
height={scrollbarHeight}
value={scrollbarIndex}
/>
</div>
);
}, areEqual);

function _getCornerstoneStack(displaySet, dataSource) {
// Get stack from Stack Manager
const storedStack = StackManager.findOrCreateStack(displaySet, dataSource);

// Clone the stack here so we don't mutate it
const stack = Object.assign({}, storedStack);

return stack;
}

function _getViewportData(dataSource, displaySets, viewportType) {
viewportType = viewportType || STACK;
if (viewportType !== STACK) {
throw new Error('Only STACK viewport type is supported now');
}

// For Stack Viewport we don't have fusion currently
const displaySet = displaySets[0];

const stack = _getCornerstoneStack(displaySet, dataSource);

const viewportData = {
StudyInstanceUID: displaySet.StudyInstanceUID,
displaySetInstanceUID: displaySet.displaySetInstanceUID,
stack,
};

return viewportData;
}

function _subscribeToJumpToMeasurementEvents(
MeasurementService,
DisplaySetService,
Expand Down Expand Up @@ -261,6 +357,7 @@ function _jumpToMeasurement(
};
utilities.jumpToSlice(targetElement, metadata);

annotations.selection.setAnnotationSelected();
// Jump to measurement consumed, remove.
MeasurementService.removeJumpToMeasurement(viewportIndex);
}
Expand Down
Loading

0 comments on commit e943f60

Please sign in to comment.