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:
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', {});
|
||||
}
|
||||
+1
-1
@@ -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', {
|
||||
|
||||
+33
-2
@@ -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) =>
|
||||
|
||||
+3
-1
@@ -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({
|
||||
|
||||
+3
-1
@@ -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,
|
||||
|
||||
+28
-17
@@ -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? */}
|
||||
|
||||
Reference in new issue
Block a user