Fix/update branch cherry pick (#2039)

* Update all the icons

* Turn off global tool sync; watch as the world burns

* Shift a bunch of things around so we can start tracking/setting per element

* ToolBarService to initiate one of three different calls; callback passed to all button types

* SplitButton and Toolbar Button to use new `onInteraction` prop and new toolbar state

* Changes to toolbar button interface and config

* Fix broken layout selector

* Update SR viewport to activate tools in viewport component

* Duplicate activation logic in measurement tracking extension

* Add alternative/dashed variants for SR Viewport

* pass through "setToolActive" commands for other viewport types

* fix small overlay bugs (no wwwc or scale info)

* Show SpacingBetweenSlices instead of PixelSpacing in patient information dialog

* Fix prop-types

* Update tracked viewport to have alternative tracked styling

* alt styling for SR viewports

* Update to support isLocked + isRehydratable

* fix broken logic

* fix broken logic

* switch icon style

* fix icon styles

* hover and click to start flow

* expedited workflow when data is not dirty (just after SR hydration)

* fix: setting elliptical roi tool

* fix arrow annotate dialog

* fix: do not show learn more button for now

* remove dead code

* simpler cache invalidation

* simpler cache invalidation part 2

* Fix for unable to spand study cards on separate pages
This commit is contained in:
Danny Brown authored and GitHub committed 2020-09-14 21:18:46 -04:00
1 parent 365ba2fa72
commit cae54b0779
78 files changed
+1678 -1263

No files matched your search

@@ -0,0 +1,36 @@
import csTools from 'cornerstone-tools';
const toolsGroupedByType = {
touch: [csTools.PanMultiTouchTool, csTools.ZoomTouchPinchTool],
annotations: [
csTools.ArrowAnnotateTool,
csTools.BidirectionalTool,
csTools.LengthTool,
csTools.AngleTool,
csTools.FreehandRoiTool,
csTools.EllipticalRoiTool,
csTools.DragProbeTool,
csTools.RectangleRoiTool,
],
other: [
csTools.PanTool,
csTools.ZoomTool,
csTools.WwwcTool,
csTools.WwwcRegionTool,
csTools.MagnifyTool,
csTools.StackScrollTool,
csTools.StackScrollMouseWheelTool,
csTools.OverlayTool,
],
};
export default function getTools() {
const tools = [];
Object.keys(toolsGroupedByType).forEach(toolsGroup =>
tools.push(...toolsGroupedByType[toolsGroup])
);
return tools;
}
export { toolsGroupedByType };
@@ -0,0 +1,21 @@
import csTools from 'cornerstone-tools';
export default function _setActiveAndPassiveToolsForElement(element, tools) {
const BaseAnnotationTool = csTools.importInternal('base/BaseAnnotationTool');
tools.forEach(tool => {
if (tool.prototype instanceof BaseAnnotationTool) {
// BaseAnnotationTool would likely come from csTools lib exports
const toolName = new tool().name;
csTools.setToolPassiveForElement(element, toolName); // there may be a better place to determine name; may not be on uninstantiated class
}
});
csTools.setToolActiveForElement(element, 'Pan', { mouseButtonMask: 4 });
csTools.setToolActiveForElement(element, 'Zoom', { mouseButtonMask: 2 });
csTools.setToolActiveForElement(element, 'Wwwc', { mouseButtonMask: 1 });
csTools.setToolActiveForElement(element, 'StackScrollMouseWheel', {}); // TODO: Empty options should not be required
csTools.setToolActiveForElement(element, 'PanMultiTouch', { pointers: 2 }); // TODO: Better error if no options
csTools.setToolActiveForElement(element, 'ZoomTouchPinch', {});
csTools.setToolEnabledForElement(element, 'Overlay', {});
}
@@ -141,7 +141,7 @@ function TrackedMeasurementsContextProvider(
if (
displaySet.SOPClassHandlerId ===
'org.ohif.dicom-sr.sopClassHandlerModule.dicom-sr' &&
!displaySet.isLocked
displaySet.isRehydratable === true
) {
console.log('sending event...', trackedMeasurements);
sendTrackedMeasurementsEvent('PROMPT_HYDRATE_SR', {
@@ -21,6 +21,8 @@ const machineConfiguration = {
prevTrackedStudy: '',
prevTrackedSeries: [],
prevIgnoredSeries: [],
//
isDirty: false,
},
states: {
off: {
@@ -33,7 +35,7 @@ const machineConfiguration = {
SET_TRACKED_SERIES: [
{
target: 'tracking',
actions: ['setTrackedStudyAndMultipleSeries'],
actions: ['setTrackedStudyAndMultipleSeries', 'setIsDirtyToClean'],
},
],
PROMPT_HYDRATE_SR: 'promptHydrateStructuredReport',
@@ -90,6 +92,16 @@ const machineConfiguration = {
},
],
SAVE_REPORT: 'promptSaveReport',
SET_DIRTY: [
{
target: 'tracking',
actions: ['setIsDirty'],
cond: 'shouldSetDirty',
},
{
target: 'tracking',
},
],
},
},
promptTrackNewSeries: {
@@ -98,7 +110,7 @@ const machineConfiguration = {
onDone: [
{
target: 'tracking',
actions: ['addTrackedSeries'],
actions: ['addTrackedSeries', 'setIsDirty'],
cond: 'shouldAddSeries',
},
{
@@ -106,6 +118,7 @@ const machineConfiguration = {
actions: [
'discardPreviouslyTrackedMeasurements',
'setTrackedStudyAndSeries',
'setIsDirty',
],
cond: 'shouldSetStudyAndSeries',
},
@@ -131,6 +144,7 @@ const machineConfiguration = {
actions: [
'discardPreviouslyTrackedMeasurements',
'setTrackedStudyAndSeries',
'setIsDirty',
],
cond: 'shouldSetStudyAndSeries',
},
@@ -197,6 +211,7 @@ const machineConfiguration = {
actions: [
'setTrackedStudyAndMultipleSeries',
'showSeriesInActiveViewport',
'setIsDirtyToClean',
],
cond: 'shouldHydrateStructuredReport',
},
@@ -274,6 +289,15 @@ const defaultOptions = {
ignoredSeries: [],
};
}),
setIsDirtyToClean: assign((ctx, evt) => ({
isDirty: false,
})),
setIsDirty: assign((ctx, evt) => {
debugger;
return {
isDirty: true,
};
}),
ignoreSeries: assign((ctx, evt) => ({
prevIgnoredSeries: [...ctx.ignoredSeries],
ignoredSeries: [...ctx.ignoredSeries, evt.data.SeriesInstanceUID],
@@ -292,6 +316,13 @@ const defaultOptions = {
})),
},
guards: {
shouldSetDirty: (ctx, evt) => {
debugger;
return (
evt.SeriesInstanceUID === undefined ||
ctx.trackedSeries.includes(evt.SeriesInstanceUID)
);
},
shouldKillMachine: (ctx, evt) =>
evt.data && evt.data.userResponse === RESPONSE.NO_NEVER,
shouldAddSeries: (ctx, evt) =>
@@ -17,11 +17,13 @@ function promptUser({ servicesManager, extensionManager }, ctx, evt) {
viewportIndex
);
if (promptResult === RESPONSE.CREATE_REPORT) {
if (ctx.isDirty && promptResult === RESPONSE.CREATE_REPORT) {
promptResult = await _askSaveDiscardOrCancel(
UIViewportDialogService,
viewportIndex
);
} else {
promptResult = RESPONSE.SET_STUDY_AND_SERIES;
}
resolve({
@@ -17,11 +17,13 @@ function promptUser({ servicesManager, extensionManager }, ctx, evt) {
viewportIndex
);
if (promptResult === RESPONSE.SET_STUDY_AND_SERIES) {
if (ctx.isDirty && promptResult === RESPONSE.SET_STUDY_AND_SERIES) {
promptResult = await _askSaveDiscardOrCancel(
UIViewportDialogService,
viewportIndex
);
} else {
promptResult = RESPONSE.SET_STUDY_AND_SERIES;
}
resolve({
@@ -10,4 +10,25 @@ export default {
getContextModule,
getPanelModule,
getViewportModule,
getCommandsModule({ servicesManager }) {
return {
definitions: {
setToolActive: {
commandFn: ({ toolName, element }) => {
if (!toolName) {
console.warn('No toolname provided to setToolActive command');
}
// Set same tool or alt tool
cornerstoneTools.setToolActiveForElement(element, toolName, {
mouseButtonMask: 1,
});
},
storeContexts: [],
options: {},
},
},
defaultContext: 'ACTIVE_VIEWPORT::TRACKED',
};
},
};
@@ -361,8 +361,11 @@ function _getOneBasedImageIdIndex(displaySets, SOPInstanceUID) {
/**
*
* @param {*} points
* @param {*} measurement
* @param {*} pixelSpacing
* @param {*} seriesNumber
* @param {*} instanceNumber
* @param {*} types
*/
function _getDisplayText(
measurement,
@@ -1,7 +1,12 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { utils } from '@ohif/core';
import { StudyBrowser, useImageViewer, useViewportGrid, Dialog } from '@ohif/ui';
import {
StudyBrowser,
useImageViewer,
useViewportGrid,
Dialog,
} from '@ohif/ui';
import { useTrackedMeasurements } from '../../getContextModule';
const { formatDate } = utils;
@@ -63,6 +68,7 @@ function PanelStudyBrowserTracking({
referenceStudyUID: StudyInstanceUID,
} = measurement;
sendTrackedMeasurementsEvent('SET_DIRTY', { SeriesInstanceUID });
sendTrackedMeasurementsEvent('TRACK_SERIES', {
viewportIndex: activeViewportIndex,
StudyInstanceUID,
@@ -238,10 +244,10 @@ function PanelStudyBrowserTracking({
);
const updatedExpandedStudyInstanceUIDs = shouldCollapseStudy
? [
...expandedStudyInstanceUIDs.filter(
stdyUid => stdyUid !== StudyInstanceUID
),
]
...expandedStudyInstanceUIDs.filter(
stdyUid => stdyUid !== StudyInstanceUID
),
]
: [...expandedStudyInstanceUIDs, StudyInstanceUID];
setExpandedStudyInstanceUIDs(updatedExpandedStudyInstanceUIDs);
@@ -315,7 +321,7 @@ function PanelStudyBrowserTracking({
SeriesInstanceUID: displaySet.SeriesInstanceUID,
});
}}
onClickThumbnail={() => { }}
onClickThumbnail={() => {}}
onDoubleClickThumbnail={onDoubleClickThumbnailHandler}
activeDisplaySetInstanceUID={activeDisplaySetInstanceUID}
/>
@@ -385,11 +391,11 @@ function _mapDisplaySets(
const viewportIdentificator = isSingleViewport
? []
: viewports.reduce((acc, viewportData, index) => {
if (viewportData.displaySetInstanceUID === ds.displaySetInstanceUID) {
acc.push(_viewportLabels[index]);
}
return acc;
}, []);
if (viewportData.displaySetInstanceUID === ds.displaySetInstanceUID) {
acc.push(_viewportLabels[index]);
}
return acc;
}, []);
const array =
componentType === 'thumbnailTracked'
@@ -430,7 +436,7 @@ function _mapDisplaySets(
contentProps: {
title: 'Reject Report',
body: () => (
<div className="p-4 bg-primary-dark text-white">
<div className="p-4 text-white bg-primary-dark">
<p>This is a destructive action.</p>
<p>Are you sure you want to continue?</p>
</div>
@@ -444,7 +450,10 @@ function _mapDisplaySets(
switch (action.id) {
case 'save':
try {
await dataSource.reject.series(ds.StudyInstanceUID, ds.SeriesInstanceUID);
await dataSource.reject.series(
ds.StudyInstanceUID,
ds.SeriesInstanceUID
);
DisplaySetService.deleteDisplaySet(displaySetInstanceUID);
UIDialogService.dismiss({ id: 'ds-reject-sr' });
UINotificationService.show({
@@ -525,9 +534,11 @@ function _createStudyBrowserTabs(
const displaySetsForStudy = displaySets.filter(
ds => ds.StudyInstanceUID === study.studyInstanceUid
);
// Sort them
const sortedDisplaySetsForStudy = utils.sortBySeriesDate(displaySetsForStudy);
// Sort them
const sortedDisplaySetsForStudy = utils.sortBySeriesDate(
displaySetsForStudy
);
/* Sort by series number, then by series date
displaySetsForStudy.sort((a, b) => {
@@ -541,7 +552,7 @@ function _createStudyBrowserTabs(
return seriesDateA - seriesDateB;
});
*/
// Map the study to it's tab/view representation
const tabStudy = Object.assign({}, study, {
displaySets: displaySetsForStudy,
@@ -16,6 +16,8 @@ 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;
@@ -44,7 +46,7 @@ function TrackedCornerstoneViewport({
displaySet,
viewportIndex,
servicesManager,
commandsManager
commandsManager,
}) {
const {
ToolBarService,
@@ -52,7 +54,10 @@ function TrackedCornerstoneViewport({
MeasurementService,
} = servicesManager.services;
const [trackedMeasurements] = useTrackedMeasurements();
const [{ activeViewportIndex, viewports }, viewportGridService] = useViewportGrid();
const [
{ activeViewportIndex, viewports },
viewportGridService,
] = useViewportGrid();
const [{ isCineEnabled, cines }, cineService] = useCine();
const [viewportDialogState, viewportDialogApi] = useViewportDialog();
const [viewportData, setViewportData] = useState(null);
@@ -126,9 +131,17 @@ function TrackedCornerstoneViewport({
const onElementEnabled = evt => {
const eventData = evt.detail;
const targetElement = eventData.element;
const tools = getTools();
const toolAlias = ToolBarService.state.primaryToolId;
// Activate appropriate tool bindings for element
setActiveAndPassiveToolsForElement(targetElement, tools);
cornerstoneTools.setToolActiveForElement(targetElement, toolAlias, {
mouseButtonMask: 1,
});
// Set dashed, based on tracking, for this viewport
const allTools = cornerstoneTools.store.state.tools;
const toolsForElement = allTools.filter(
tool => tool.element === targetElement
);
@@ -154,6 +167,7 @@ function TrackedCornerstoneViewport({
}
});
// Update image after setting tool config
const enabledElement = cornerstone.getEnabledElement(targetElement);
if (enabledElement.image) {
@@ -166,6 +180,7 @@ function TrackedCornerstoneViewport({
'ohif-cornerstone-enabled-element-event',
{
detail: {
context: 'ACTIVE_VIEWPORT::TRACKED',
enabledElement: targetElement,
viewportIndex,
},
@@ -256,7 +271,7 @@ function TrackedCornerstoneViewport({
PatientSex,
PatientAge,
SliceThickness,
PixelSpacing,
SpacingBetweenSlices,
ManufacturerModelName,
} = displaySet.images[0];
@@ -300,8 +315,8 @@ function TrackedCornerstoneViewport({
}
const cine = cines[viewportIndex];
const isPlaying = cine && cine.isPlaying || false;
const frameRate = cine && cine.frameRate || 24;
const isPlaying = (cine && cine.isPlaying) || false;
const frameRate = (cine && cine.frameRate) || 24;
return (
<>
@@ -315,6 +330,7 @@ function TrackedCornerstoneViewport({
label,
isTracked,
isLocked: false,
isRehydratable: false,
studyDate: formatDate(SeriesDate), // TODO: This is series date. Is that ok?
currentSeries: SeriesNumber,
seriesDescription: SeriesDescription,
@@ -328,10 +344,8 @@ function TrackedCornerstoneViewport({
MRN: PatientID || '',
thickness: SliceThickness ? `${SliceThickness.toFixed(2)}mm` : '',
spacing:
PixelSpacing && PixelSpacing.length
? `${PixelSpacing[0].toFixed(2)}mm x ${PixelSpacing[1].toFixed(
2
)}mm`
SpacingBetweenSlices !== undefined
? `${SpacingBetweenSlices.toFixed(2)}mm`
: '',
scanner: ManufacturerModelName || '',
},
@@ -341,8 +355,10 @@ function TrackedCornerstoneViewport({
cineProps={{
isPlaying,
onClose: () => commandsManager.runCommand('toggleCine'),
onPlayPauseChange: isPlaying => cineService.setCine({ id: activeViewportIndex, isPlaying }),
onFrameRateChange: frameRate => cineService.setCine({ id: activeViewportIndex, frameRate }),
onPlayPauseChange: isPlaying =>
cineService.setCine({ id: activeViewportIndex, isPlaying }),
onFrameRateChange: frameRate =>
cineService.setCine({ id: activeViewportIndex, frameRate }),
}}
/>
{/* TODO: Viewport interface to accept stack or layers of content like this? */}