feat: Add White Labelling, Hanging Protocols, and tons of Extensions / Mode fixes

* modified

* new files in existing extensions/libraries/modes

* fixed bugs and tests

* Clean up

* fix: viewport display fixed

* Fixed configs for deployment

* Removed unnecessary files and functions

* Fixed default HP module

* Added white labelling

* Fixed hash routing

* Removed unnecessary routers

Co-authored-by: Alireza Sedghi <ar.sedghi@gmail.com>
This commit is contained in:
Erik ZieglerandAlireza Sedghi authored and GitHub committed 2021-05-28 14:32:48 -04:00
1 parent 0db81b30f3
commit 99b8dc4759
93 files changed
+3825 -1293

No files matched your search

@@ -1,8 +1,7 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useState, useRef } from 'react';
import PropTypes from 'prop-types';
import cornerstone from 'cornerstone-core';
import cornerstoneTools from 'cornerstone-tools';
import CornerstoneViewport from 'react-cornerstone-viewport';
import OHIF, { utils } from '@ohif/core';
import {
Notification,
@@ -12,14 +11,10 @@ import {
useViewportDialog,
} from '@ohif/ui';
import { useTrackedMeasurements } from './../getContextModule';
import ViewportOverlay from './ViewportOverlay';
import ViewportLoadingIndicator from './ViewportLoadingIndicator';
import setCornerstoneMeasurementActive from '../_shared/setCornerstoneMeasurementActive';
import setActiveAndPassiveToolsForElement from '../_shared/setActiveAndPassiveToolsForElement';
import getTools from '../_shared/getTools';
const scrollToIndex = cornerstoneTools.importInternal('util/scrollToIndex');
const { formatDate } = utils;
// TODO -> Get this list from the list of tracked measurements.
@@ -40,19 +35,19 @@ const BaseAnnotationTool = cornerstoneTools.importInternal(
const { StackManager } = OHIF.utils;
function TrackedCornerstoneViewport({
children,
dataSource,
displaySet,
viewportIndex,
servicesManager,
commandsManager,
}) {
function TrackedCornerstoneViewport(props) {
const {
ToolBarService,
DisplaySetService,
MeasurementService,
} = servicesManager.services;
children,
dataSource,
displaySet,
viewportIndex,
servicesManager,
extensionManager,
commandsManager,
} = props;
const { ToolBarService } = servicesManager.services;
const [trackedMeasurements] = useTrackedMeasurements();
const [
{ activeViewportIndex, viewports },
@@ -60,73 +55,9 @@ function TrackedCornerstoneViewport({
] = useViewportGrid();
const [{ isCineEnabled, cines }, cineService] = useCine();
const [viewportDialogState, viewportDialogApi] = useViewportDialog();
const [viewportData, setViewportData] = useState(null);
const [element, setElement] = useState(null);
const [isTracked, setIsTracked] = useState(false);
const [trackedMeasurementId, setTrackedMeasurementId] = useState(null);
// TODO: Still needed? Better way than import `OHIF` and destructure?
// Why is this managed by `core`?
useEffect(() => {
return () => {
StackManager.clearStacks();
};
}, []);
useEffect(() => {
cineService.setCine({ id: viewportIndex });
}, [viewportIndex]);
useEffect(() => {
const unsubscribeFromJumpToMeasurementEvents = _subscribeToJumpToMeasurementEvents(
MeasurementService,
DisplaySetService,
element,
viewportIndex,
displaySet.displaySetInstanceUID
);
_checkForCachedJumpToMeasurementEvents(
MeasurementService,
DisplaySetService,
element,
viewportIndex,
displaySet.displaySetInstanceUID
);
return () => {
unsubscribeFromJumpToMeasurementEvents();
};
}, [element, displaySet]);
useEffect(() => {
if (!element) {
return;
}
const allTools = cornerstoneTools.store.state.tools;
const toolsForElement = allTools.filter(tool => tool.element === element);
toolsForElement.forEach(tool => {
if (
tool instanceof ArrowAnnotateTool ||
tool instanceof BidirectionalTool ||
tool instanceof EllipticalRoiTool ||
tool instanceof LengthTool
) {
const configuration = tool.configuration;
configuration.renderDashed = !isTracked;
tool.configuration = configuration;
}
});
const enabledElement = cornerstone.getEnabledElement(element);
if (enabledElement.image) {
cornerstone.updateImage(element);
}
}, [isTracked]);
const [element, setElement] = useState(null);
const onElementEnabled = evt => {
const eventData = evt.detail;
@@ -191,51 +122,33 @@ function TrackedCornerstoneViewport({
};
useEffect(() => {
const {
StudyInstanceUID,
displaySetInstanceUID,
sopClassUids,
} = displaySet;
if (!StudyInstanceUID || !displaySetInstanceUID) {
if (!element) {
return;
}
const allTools = cornerstoneTools.store.state.tools;
const toolsForElement = allTools.filter(tool => tool.element === element);
if (sopClassUids && sopClassUids.length > 1) {
console.warn(
'More than one SOPClassUID in the same series is not yet supported.'
);
}
toolsForElement.forEach(tool => {
if (
tool instanceof ArrowAnnotateTool ||
tool instanceof BidirectionalTool ||
tool instanceof EllipticalRoiTool ||
tool instanceof LengthTool
) {
const configuration = tool.configuration;
_getViewportData(dataSource, displaySet).then(setViewportData);
}, [dataSource, displaySet, viewports, viewportIndex]);
configuration.renderDashed = !isTracked;
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
let childrenWithProps = null;
if (!viewportData) {
return null;
}
const {
imageIds,
currentImageIdIndex,
// If this comes from the instance, would be a better default
// `FrameTime` in the instance
// frameRate = 0,
} = viewportData.stack;
if (children && children.length) {
childrenWithProps = children.map((child, index) => {
return (
child &&
React.cloneElement(child, {
viewportIndex,
key: index,
})
);
tool.configuration = configuration;
}
});
}
const enabledElement = cornerstone.getEnabledElement(element);
if (enabledElement.image) {
cornerstone.updateImage(element);
}
}, [isTracked]);
// We have...
// StudyInstanceUid, DisplaySetInstanceUid
@@ -313,6 +226,19 @@ function TrackedCornerstoneViewport({
);
}
const renderViewport = () => {
const { component: Component } = extensionManager.getModuleEntry(
'org.ohif.cornerstone.viewportModule.cornerstone'
);
return (
<Component
onElementEnabled={onElementEnabled}
element={element}
{...props}
></Component>
);
};
const cine = cines[viewportIndex];
const isPlaying = (cine && cine.isPlaying) || false;
const frameRate = (cine && cine.frameRate) || 24;
@@ -341,10 +267,12 @@ function TrackedCornerstoneViewport({
patientSex: PatientSex || '',
patientAge: PatientAge || '',
MRN: PatientID || '',
thickness: SliceThickness ? `${SliceThickness.toFixed(2)}mm` : '',
thickness: SliceThickness
? `${parseFloat(SliceThickness).toFixed(2)}mm`
: '',
spacing:
SpacingBetweenSlices !== undefined
? `${SpacingBetweenSlices.toFixed(2)}mm`
? `${parseFloat(SpacingBetweenSlices).toFixed(2)}mm`
: '',
scanner: ManufacturerModelName || '',
},
@@ -362,29 +290,7 @@ function TrackedCornerstoneViewport({
/>
{/* TODO: Viewport interface to accept stack or layers of content like this? */}
<div className="relative flex flex-row w-full h-full overflow-hidden">
<CornerstoneViewport
onElementEnabled={onElementEnabled}
viewportIndex={viewportIndex}
imageIds={imageIds}
imageIdIndex={currentImageIdIndex}
// Sync resize throttle w/ sidepanel animation duration to prevent
// seizure inducing strobe blinking effect
resizeRefreshRateMs={150}
isActive={true} // todo
isStackPrefetchEnabled={true} // todo
isPlaying={isPlaying}
frameRate={frameRate}
isOverlayVisible={true}
loadingIndicatorComponent={ViewportLoadingIndicator}
viewportOverlayComponent={props => {
return (
<ViewportOverlay
{...props}
activeTools={ToolBarService.getActiveTools()}
/>
);
}}
/>
{renderViewport()}
<div className="absolute w-full">
{viewportDialogState.viewportIndex === viewportIndex && (
<Notification
@@ -397,7 +303,6 @@ function TrackedCornerstoneViewport({
/>
)}
</div>
{childrenWithProps}
</div>
</>
);
@@ -417,54 +322,6 @@ TrackedCornerstoneViewport.defaultProps = {
const _viewportLabels = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'];
/**
* Obtain the CornerstoneTools Stack for the specified display set.
*
* @param {Object} displaySet
* @param {Object} dataSource
* @return {Object} CornerstoneTools Stack
*/
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;
}
// TODO -> disabled double click for now: onDoubleClick={_onDoubleClick}
function _onDoubleClick() {
const cancelActiveManipulatorsForElement = cornerstoneTools.getModule(
'manipulatorState'
).setters.cancelActiveManipulatorsForElement;
const enabledElements = cornerstoneTools.store.state.enabledElements;
enabledElements.forEach(element => {
cancelActiveManipulatorsForElement(element);
});
}
/**
* Builds the viewport data from a datasource and a displayset.
*
* @param {Object} dataSource
* @param {Object} displaySet
* @return {Object} viewport data
*/
async function _getViewportData(dataSource, displaySet) {
const stack = _getCornerstoneStack(displaySet, dataSource);
const viewportData = {
StudyInstanceUID: displaySet.StudyInstanceUID,
displaySetInstanceUID: displaySet.displaySetInstanceUID,
stack,
};
return viewportData;
}
function _getNextMeasurementId(
direction,
servicesManager,
@@ -518,112 +375,4 @@ function _getNextMeasurementId(
return newTrackedMeasurementId;
}
function _subscribeToJumpToMeasurementEvents(
MeasurementService,
DisplaySetService,
element,
viewportIndex,
displaySetInstanceUID
) {
const { unsubscribe } = MeasurementService.subscribe(
MeasurementService.EVENTS.JUMP_TO_MEASUREMENT,
({ viewportIndex: jumpToMeasurementViewportIndex, measurement }) => {
// check if the correct viewport index.
if (viewportIndex !== jumpToMeasurementViewportIndex) {
// Event for a different viewport.
return;
}
if (measurement.displaySetInstanceUID !== displaySetInstanceUID) {
// Not for this displaySet.
return;
}
_jumpToMeasurement(
measurement,
element,
viewportIndex,
MeasurementService,
DisplaySetService
);
}
);
return unsubscribe;
}
function _checkForCachedJumpToMeasurementEvents(
MeasurementService,
DisplaySetService,
element,
viewportIndex,
displaySetInstanceUID
) {
// Check if there is a queued jumpToMeasurement event
const measurementIdToJumpTo = MeasurementService.getJumpToMeasurement(
viewportIndex
);
if (measurementIdToJumpTo && element) {
// Jump to measurement if the measurement exists
const measurement = MeasurementService.getMeasurement(
measurementIdToJumpTo
);
if (measurement.displaySetInstanceUID === displaySetInstanceUID) {
_jumpToMeasurement(
measurement,
element,
viewportIndex,
MeasurementService,
DisplaySetService
);
}
}
}
function _jumpToMeasurement(
measurement,
targetElement,
viewportIndex,
MeasurementService,
DisplaySetService
) {
const { displaySetInstanceUID, SOPInstanceUID } = measurement;
const referencedDisplaySet = DisplaySetService.getDisplaySetByUID(
displaySetInstanceUID
);
const imageIndex = referencedDisplaySet.images.findIndex(
i => i.SOPInstanceUID === SOPInstanceUID
);
setCornerstoneMeasurementActive(measurement);
if (targetElement !== null) {
const enabledElement = cornerstone.getEnabledElement(targetElement);
// Wait for the image to update or we get a race condition when the element has only just been enabled.
const scrollToHandler = evt => {
scrollToIndex(targetElement, imageIndex);
targetElement.removeEventListener(
cornerstone.EVENTS.IMAGE_RENDERED,
scrollToHandler
);
};
targetElement.addEventListener(
cornerstone.EVENTS.IMAGE_RENDERED,
scrollToHandler
);
if (enabledElement.image) {
cornerstone.updateImage(targetElement);
}
// Jump to measurement consumed, remove.
MeasurementService.removeJumpToMeasurement(viewportIndex);
}
}
export default TrackedCornerstoneViewport;