feat(segmentation mode): Add create, and export SEG with Brushes (#3632)

This commit is contained in:
Alireza authored and GitHub committed 2023-09-22 10:23:44 -04:00
1 parent 896866e9d0
commit 48bbd6281a
186 files changed
+6801 -2883

No files matched your search

@@ -1,69 +0,0 @@
import React from 'react';
import { DicomMetadataStore } from '@ohif/core';
/**
*
* @param {*} servicesManager
* @param {*} dataSource
* @param {*} measurements
* @param {*} options
* @returns {string[]} displaySetInstanceUIDs
*/
async function createReportAsync(
servicesManager,
commandsManager,
dataSource,
measurements,
options
) {
const { displaySetService, uiNotificationService, uiDialogService } = servicesManager.services;
const loadingDialogId = uiDialogService.create({
showOverlay: true,
isDraggable: false,
centralize: true,
// TODO: Create a loading indicator component + zeplin design?
content: Loading,
});
try {
const naturalizedReport = await commandsManager.runCommand(
'storeMeasurements',
{
measurementData: measurements,
dataSource,
additionalFindingTypes: ['ArrowAnnotate'],
options,
},
'CORNERSTONE_STRUCTURED_REPORT'
);
// The "Mode" route listens for DicomMetadataStore changes
// When a new instance is added, it listens and
// automatically calls makeDisplaySets
DicomMetadataStore.addInstances([naturalizedReport], true);
const displaySetInstanceUID = displaySetService.getMostRecentDisplaySet();
uiNotificationService.show({
title: 'Create Report',
message: 'Measurements saved successfully',
type: 'success',
});
return [displaySetInstanceUID];
} catch (error) {
uiNotificationService.show({
title: 'Create Report',
message: error.message || 'Failed to store measurements',
type: 'error',
});
} finally {
uiDialogService.dismiss({ id: loadingDialogId });
}
}
function Loading() {
return <div className="text-primary-active">Loading...</div>;
}
export default createReportAsync;
@@ -1,87 +0,0 @@
/* eslint-disable react/display-name */
import React from 'react';
import { ButtonEnums, Dialog, Input } from '@ohif/ui';
import RESPONSE from './PROMPT_RESPONSES';
export default function createReportDialogPrompt(uiDialogService) {
return new Promise(function (resolve, reject) {
let dialogId = undefined;
const _handleClose = () => {
// Dismiss dialog
uiDialogService.dismiss({ id: dialogId });
// Notify of cancel action
resolve({ action: RESPONSE.CANCEL, value: undefined });
};
/**
*
* @param {string} param0.action - value of action performed
* @param {string} param0.value - value from input field
*/
const _handleFormSubmit = ({ action, value }) => {
switch (action.id) {
case 'save':
// Only save if description is not blank otherwise ignore
if (value.label && value.label.trim() !== '') {
resolve({
action: RESPONSE.CREATE_REPORT,
value: value.label.trim(),
});
uiDialogService.dismiss({ id: dialogId });
}
break;
case 'cancel':
uiDialogService.dismiss({ id: dialogId });
resolve({ action: RESPONSE.CANCEL, value: undefined });
break;
}
};
dialogId = uiDialogService.create({
centralize: true,
isDraggable: false,
content: Dialog,
useLastPosition: false,
showOverlay: true,
contentProps: {
title: 'Create Report',
value: { label: '' },
noCloseButton: true,
onClose: _handleClose,
actions: [
{ id: 'cancel', text: 'Cancel', type: ButtonEnums.type.secondary },
{ id: 'save', text: 'Save', type: ButtonEnums.type.primary },
],
// TODO: Should be on button press...
onSubmit: _handleFormSubmit,
body: ({ value, setValue }) => {
const onChangeHandler = event => {
event.persist();
setValue(value => ({ ...value, label: event.target.value }));
};
const onKeyPressHandler = event => {
if (event.key === 'Enter') {
// Trigger form submit
_handleFormSubmit({ action: { id: 'save' }, value });
}
};
return (
<div className="">
<Input
label="Enter the report name"
labelClassName="text-white grow leading-[1.2] text-[14px]"
autoFocus
className="border-primary-main grow bg-black"
type="text"
value={value.label}
onChange={onChangeHandler}
onKeyPress={onKeyPressHandler}
/>
</div>
);
},
},
});
});
}
@@ -35,7 +35,7 @@ function TrackedMeasurementsContextProvider(
const machineOptions = Object.assign({}, defaultOptions);
machineOptions.actions = Object.assign({}, machineOptions.actions, {
jumpToFirstMeasurementInActiveViewport: (ctx, evt) => {
const { trackedStudy, trackedSeries } = ctx;
const { trackedStudy, trackedSeries, activeViewportId } = ctx;
const measurements = measurementService.getMeasurements();
const trackedMeasurements = measurements.filter(
m => trackedStudy === m.referenceStudyUID && trackedSeries.includes(m.referenceSeriesUID)
@@ -43,7 +43,7 @@ function TrackedMeasurementsContextProvider(
console.log(
'jumping to measurement reset viewport',
viewportGrid.activeViewportId,
activeViewportId,
trackedMeasurements[0]
);
@@ -71,7 +71,7 @@ function TrackedMeasurementsContextProvider(
}
viewportGridService.setDisplaySetsForViewport({
viewportId: viewportGrid.activeViewportId,
viewportId: activeViewportId,
displaySetInstanceUIDs: [referencedDisplaySetUID],
viewportOptions: {
initialImageOptions: {
@@ -82,8 +82,7 @@ function TrackedMeasurementsContextProvider(
},
showStructuredReportDisplaySetInActiveViewport: (ctx, evt) => {
if (evt.data.createdDisplaySetInstanceUIDs.length > 0) {
const StructuredReportDisplaySetInstanceUID =
evt.data.createdDisplaySetInstanceUIDs[0].displaySetInstanceUID;
const StructuredReportDisplaySetInstanceUID = evt.data.createdDisplaySetInstanceUIDs[0];
viewportGridService.setDisplaySetsForViewport({
viewportId: evt.data.viewportId,
@@ -160,6 +159,13 @@ function TrackedMeasurementsContextProvider(
measurementTrackingMachine
);
useEffect(() => {
// Update the state machine with the active viewport ID
sendTrackedMeasurementsEvent('UPDATE_ACTIVE_VIEWPORT_ID', {
activeViewportId,
});
}, [activeViewportId, sendTrackedMeasurementsEvent]);
// ~~ Listen for changes to ViewportGrid for potential SRs hung in panes when idle
useEffect(() => {
if (viewports.size > 0) {
@@ -15,6 +15,7 @@ const machineConfiguration = {
id: 'measurementTracking',
initial: 'idle',
context: {
activeViewportId: null,
trackedStudy: '',
trackedSeries: [],
ignoredSeries: [],
@@ -47,6 +48,11 @@ const machineConfiguration = {
},
RESTORE_PROMPT_HYDRATE_SR: 'promptHydrateStructuredReport',
HYDRATE_SR: 'hydrateStructuredReport',
UPDATE_ACTIVE_VIEWPORT_ID: {
actions: assign({
activeViewportId: (_, event) => event.activeViewportId,
}),
},
},
},
promptBeginTracking: {
@@ -1,5 +1,4 @@
import createReportAsync from './../../_shared/createReportAsync';
import createReportDialogPrompt from '../../_shared/createReportDialogPrompt';
import { createReportAsync, createReportDialogPrompt } from '@ohif/extension-default';
import getNextSRSeriesNumber from '../../_shared/getNextSRSeriesNumber';
import RESPONSE from '../../_shared/PROMPT_RESPONSES';
@@ -15,7 +14,9 @@ function promptSaveReport({ servicesManager, commandsManager, extensionManager }
return new Promise(async function (resolve, reject) {
// TODO: Fallback if (uiDialogService) {
const promptResult = await createReportDialogPrompt(uiDialogService);
const promptResult = await createReportDialogPrompt(uiDialogService, {
extensionManager,
});
if (promptResult.action === RESPONSE.CREATE_REPORT) {
const dataSources = extensionManager.getDataSources();
@@ -33,16 +34,25 @@ function promptSaveReport({ servicesManager, commandsManager, extensionManager }
const SeriesNumber = getNextSRSeriesNumber(displaySetService);
displaySetInstanceUIDs = await createReportAsync(
const getReport = async () => {
return commandsManager.runCommand(
'storeMeasurements',
{
measurementData: trackedMeasurements,
dataSource,
additionalFindingTypes: ['ArrowAnnotate'],
options: {
SeriesDescription,
SeriesNumber,
},
},
'CORNERSTONE_STRUCTURED_REPORT'
);
};
displaySetInstanceUIDs = await createReportAsync({
servicesManager,
commandsManager,
dataSource,
trackedMeasurements,
{
SeriesDescription,
SeriesNumber,
}
);
getReport,
});
} else if (promptResult.action === RESPONSE.CANCEL) {
// Do nothing
}
@@ -131,14 +131,16 @@ function PanelStudyBrowserTracking({
const imageIds = dataSource.getImageIdsForDisplaySet(displaySet);
const imageId = imageIds[Math.floor(imageIds.length / 2)];
// TODO: Is it okay that imageIds are not returned here for SR displaysets?
if (imageId && !displaySet?.unsupported) {
// When the image arrives, render it and store the result in the thumbnailImgSrcMap
newImageSrcEntry[dSet.displaySetInstanceUID] = await getImageSrc(imageId);
setThumbnailImageSrcMap(prevState => {
return { ...prevState, ...newImageSrcEntry };
});
// TODO: Is it okay that imageIds are not returned here for SR displaySets?
if (!imageId || displaySet?.unsupported) {
return;
}
// When the image arrives, render it and store the result in the thumbnailImgSrcMap
newImageSrcEntry[dSet.displaySetInstanceUID] = await getImageSrc(imageId);
setThumbnailImageSrcMap(prevState => {
return { ...prevState, ...newImageSrcEntry };
});
});
}, [displaySetService, dataSource, getImageSrc]);
@@ -184,27 +186,38 @@ function PanelStudyBrowserTracking({
const newImageSrcEntry = {};
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
if (!displaySet?.unsupported) {
if (options.madeInClient) {
setJumpToDisplaySet(displaySetInstanceUID);
}
const imageIds = dataSource.getImageIdsForDisplaySet(displaySet);
const imageId = imageIds[Math.floor(imageIds.length / 2)];
// TODO: Is it okay that imageIds are not returned here for SR displaysets?
if (imageId) {
// When the image arrives, render it and store the result in the thumbnailImgSrcMap
newImageSrcEntry[displaySetInstanceUID] = await getImageSrc(imageId);
setThumbnailImageSrcMap(prevState => {
return { ...prevState, ...newImageSrcEntry };
});
}
if (displaySet?.unsupported) {
return;
}
if (options.madeInClient) {
setJumpToDisplaySet(displaySetInstanceUID);
}
const imageIds = dataSource.getImageIdsForDisplaySet(displaySet);
const imageId = imageIds[Math.floor(imageIds.length / 2)];
// TODO: Is it okay that imageIds are not returned here for SR displaysets?
if (!imageId) {
return;
}
// When the image arrives, render it and store the result in the thumbnailImgSrcMap
newImageSrcEntry[displaySetInstanceUID] = await getImageSrc(imageId);
setThumbnailImageSrcMap(prevState => {
return { ...prevState, ...newImageSrcEntry };
});
});
}
);
return () => {
SubscriptionDisplaySetsAdded.unsubscribe();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [displaySetService, dataSource, getImageSrc, thumbnailImageSrcMap, trackedSeries, viewports]);
useEffect(() => {
// TODO: Will this always hold _all_ the displaySets we care about?
// DISPLAY_SETS_CHANGED returns `DisplaySerService.activeDisplaySets`
const SubscriptionDisplaySetsChanged = displaySetService.subscribe(
@@ -246,12 +259,10 @@ function PanelStudyBrowserTracking({
);
return () => {
SubscriptionDisplaySetsAdded.unsubscribe();
SubscriptionDisplaySetsChanged.unsubscribe();
SubscriptionDisplaySetMetaDataInvalidated.unsubscribe();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [displaySetService, dataSource, getImageSrc, thumbnailImageSrcMap, trackedSeries, viewports]);
}, [thumbnailImageSrcMap, trackedSeries, viewports, dataSource, displaySetService]);
const tabs = _createStudyBrowserTabs(
StudyInstanceUIDs,
@@ -442,7 +453,7 @@ function _mapDisplaySets(
body: () => (
<div className="bg-primary-dark p-4 text-white">
<p>Are you sure you want to delete this report?</p>
<p>This action cannot be undone.</p>
<p className="mt-2">This action cannot be undone.</p>
</div>
),
actions: [