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

@@ -4,49 +4,31 @@ import { DicomMetadataStore } from '@ohif/core';
/**
*
* @param {*} servicesManager
* @param {*} dataSource
* @param {*} measurements
* @param {*} options
* @returns {string[]} displaySetInstanceUIDs
*/
async function createReportAsync(
servicesManager,
commandsManager,
dataSource,
measurements,
options
) {
async function createReportAsync({ servicesManager, getReport, reportType = 'measurement' }) {
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'
);
const naturalizedReport = await getReport();
// 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();
const displaySet = displaySetService.getMostRecentDisplaySet();
const displaySetInstanceUID = displaySet.displaySetInstanceUID;
uiNotificationService.show({
title: 'Create Report',
message: 'Measurements saved successfully',
message: `${reportType} saved successfully`,
type: 'success',
});
@@ -54,7 +36,7 @@ async function createReportAsync(
} catch (error) {
uiNotificationService.show({
title: 'Create Report',
message: error.message || 'Failed to store measurements',
message: error.message || `Failed to store ${reportType}`,
type: 'error',
});
} finally {
@@ -119,18 +119,18 @@ function createDicomJSONApi(dicomJsonConfig) {
});
},
processResults: () => {
console.debug(' DICOMJson QUERY processResults');
console.warn(' DICOMJson QUERY processResults not implemented');
},
},
series: {
// mapParams: mapParams.bind(),
search: () => {
console.debug(' DICOMJson QUERY SERIES SEARCH');
console.warn(' DICOMJson QUERY SERIES SEARCH not implemented');
},
},
instances: {
search: () => {
console.debug(' DICOMJson QUERY instances SEARCH');
console.warn(' DICOMJson QUERY instances SEARCH not implemented');
},
},
},
@@ -211,7 +211,7 @@ function createDicomJSONApi(dicomJsonConfig) {
},
store: {
dicom: () => {
console.debug(' DICOMJson store dicom');
console.warn(' DICOMJson store dicom not implemented');
},
},
getImageIdsForDisplaySet(displaySet) {
@@ -85,7 +85,7 @@ function createDicomLocalApi(dicomLocalConfig) {
});
},
processResults: () => {
console.debug(' DICOMLocal QUERY processResults');
console.warn(' DICOMLocal QUERY processResults not implemented');
},
},
series: {
@@ -107,7 +107,7 @@ function createDicomLocalApi(dicomLocalConfig) {
},
instances: {
search: () => {
console.debug(' DICOMLocal QUERY instances SEARCH');
console.warn(' DICOMLocal QUERY instances SEARCH not implemented');
},
},
},
@@ -221,7 +221,7 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) {
await wadoDicomWebClient.storeInstances(options);
} else {
const meta = {
FileMetaInformationVersion: dataset._meta.FileMetaInformationVersion.Value,
FileMetaInformationVersion: dataset._meta?.FileMetaInformationVersion?.Value,
MediaStorageSOPClassUID: dataset.SOPClassUID,
MediaStorageSOPInstanceUID: dataset.SOPInstanceUID,
TransferSyntaxUID: EXPLICIT_VR_LITTLE_ENDIAN,
@@ -295,6 +295,8 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) {
});
instance.imageId = imageId;
instance.wadoRoot = dicomWebConfig.wadoRoot;
instance.wadoUri = dicomWebConfig.wadoUri;
metadataProvider.addImageIdToUIDs(imageId, {
StudyInstanceUID,
@@ -2,18 +2,18 @@ import React from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { LegacyButton, ButtonGroup } from '@ohif/ui';
import { LegacyButton, LegacyButtonGroup } from '@ohif/ui';
function ActionButtons({ onExportClick, onCreateReportClick }) {
const { t } = useTranslation('MeasurementTable');
return (
<React.Fragment>
<ButtonGroup
<LegacyButtonGroup
color="black"
size="inherit"
>
{/* TODO Revisit design of ButtonGroup later - for now use LegacyButton for its children.*/}
{/* TODO Revisit design of LegacyButtonGroup later - for now use LegacyButton for its children.*/}
<LegacyButton
className="px-2 py-2 text-base"
onClick={onExportClick}
@@ -26,7 +26,7 @@ function ActionButtons({ onExportClick, onCreateReportClick }) {
>
{t('Create Report')}
</LegacyButton>
</ButtonGroup>
</LegacyButtonGroup>
</React.Fragment>
);
}
@@ -103,13 +103,20 @@ export default function PanelMeasurementTable({
// creating too many series instances.
const options = findSRWithSameSeriesDescription(SeriesDescription, displaySetService);
return createReportAsync(
servicesManager,
commandsManager,
dataSource,
trackedMeasurements,
options
);
const getReport = async () => {
return commandsManager.runCommand(
'storeMeasurements',
{
measurementData: trackedMeasurements,
dataSource,
additionalFindingTypes: ['ArrowAnnotate'],
options,
},
'CORNERSTONE_STRUCTURED_REPORT'
);
};
return createReportAsync({ servicesManager, getReport });
}
}
@@ -103,8 +103,7 @@ function PanelStudyBrowser({
}
StudyInstanceUIDs.forEach(sid => fetchStudiesForPatient(sid));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [StudyInstanceUIDs, getStudiesForPatientByMRN]);
}, [StudyInstanceUIDs, dataSource, getStudiesForPatientByMRN, navigate]);
// // ~~ Initial Thumbnails
useEffect(() => {
@@ -116,21 +115,23 @@ function PanelStudyBrowser({
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);
if (isMounted.current) {
setThumbnailImageSrcMap(prevState => {
return { ...prevState, ...newImageSrcEntry };
});
}
if (!imageId || displaySet?.unsupported) {
return;
}
// When the image arrives, render it and store the result in the thumbnailImgSrcMap
newImageSrcEntry[dSet.displaySetInstanceUID] = await getImageSrc(imageId);
if (!isMounted.current) {
return;
}
setThumbnailImageSrcMap(prevState => {
return { ...prevState, ...newImageSrcEntry };
});
});
return () => {
isMounted.current = false;
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [StudyInstanceUIDs, dataSource, displaySetService, getImageSrc]);
// ~~ displaySets
useEffect(() => {
@@ -140,8 +141,7 @@ function PanelStudyBrowser({
sortStudyInstances(mappedDisplaySets);
setDisplaySets(mappedDisplaySets);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [thumbnailImageSrcMap]);
}, [StudyInstanceUIDs, thumbnailImageSrcMap, displaySetService]);
// ~~ subscriptions --> displaySets
useEffect(() => {
@@ -149,32 +149,40 @@ function PanelStudyBrowser({
const SubscriptionDisplaySetsAdded = displaySetService.subscribe(
displaySetService.EVENTS.DISPLAY_SETS_ADDED,
data => {
const { displaySetsAdded } = data;
const { displaySetsAdded, options } = data;
displaySetsAdded.forEach(async dSet => {
const newImageSrcEntry = {};
const displaySet = displaySetService.getDisplaySetByUID(dSet.displaySetInstanceUID);
if (!displaySet?.unsupported) {
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[dSet.displaySetInstanceUID] = await getImageSrc(
imageId,
dSet.initialViewport
);
if (isMounted.current) {
setThumbnailImageSrcMap(prevState => {
return { ...prevState, ...newImageSrcEntry };
});
}
}
if (displaySet?.unsupported) {
return;
}
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[dSet.displaySetInstanceUID] = await getImageSrc(
imageId,
dSet.initialViewport
);
setThumbnailImageSrcMap(prevState => {
return { ...prevState, ...newImageSrcEntry };
});
});
}
);
return () => {
SubscriptionDisplaySetsAdded.unsubscribe();
};
}, [getImageSrc, dataSource, displaySetService]);
useEffect(() => {
// TODO: Will this always hold _all_ the displaySets we care about?
// DISPLAY_SETS_CHANGED returns `DisplaySerService.activeDisplaySets`
const SubscriptionDisplaySetsChanged = displaySetService.subscribe(
@@ -198,12 +206,10 @@ function PanelStudyBrowser({
);
return () => {
SubscriptionDisplaySetsAdded.unsubscribe();
SubscriptionDisplaySetsChanged.unsubscribe();
SubscriptionDisplaySetMetaDataInvalidated.unsubscribe();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [StudyInstanceUIDs, thumbnailImageSrcMap, displaySetService]);
const tabs = _createStudyBrowserTabs(StudyInstanceUIDs, studyDisplayList, displaySets);
+7 -1
View File
@@ -1,5 +1,11 @@
import PanelStudyBrowser from './PanelStudyBrowser';
import WrappedPanelStudyBrowser from './WrappedPanelStudyBrowser';
import PanelMeasurementTable from './PanelMeasurementTable';
import createReportDialogPrompt from './createReportDialogPrompt';
export { PanelStudyBrowser, WrappedPanelStudyBrowser, PanelMeasurementTable };
export {
PanelStudyBrowser,
WrappedPanelStudyBrowser,
PanelMeasurementTable,
createReportDialogPrompt,
};
+10 -34
View File
@@ -1,51 +1,29 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useState, useCallback } from 'react';
import classnames from 'classnames';
export default function Toolbar({ servicesManager }) {
const { toolbarService } = servicesManager.services;
const [toolbarButtons, setToolbarButtons] = useState([]);
const [buttonState, setButtonState] = useState({
primaryToolId: '',
toggles: {},
groups: {},
});
// Could track buttons and state separately...?
useEffect(() => {
const { unsubscribe: unsub1 } = toolbarService.subscribe(
toolbarService.EVENTS.TOOL_BAR_MODIFIED,
() => setToolbarButtons(toolbarService.getButtonSection('primary'))
);
const { unsubscribe: unsub2 } = toolbarService.subscribe(
toolbarService.EVENTS.TOOL_BAR_STATE_MODIFIED,
() => setButtonState({ ...toolbarService.state })
const { unsubscribe } = toolbarService.subscribe(toolbarService.EVENTS.TOOL_BAR_MODIFIED, () =>
setToolbarButtons(toolbarService.getButtonSection('primary'))
);
return () => {
unsub1();
unsub2();
unsubscribe();
};
}, [toolbarService]);
const onInteraction = useCallback(
args => toolbarService.recordInteraction(args),
[toolbarService]
);
return (
<>
{toolbarButtons.map(toolDef => {
const { id, Component, componentProps } = toolDef;
// TODO: ...
// isActive if:
// - id is primary?
// - id is in list of "toggled on"?
let isActive;
if (componentProps.type === 'toggle') {
isActive = buttonState.toggles[id];
}
// Also need... to filter list for splitButton, and set primary based on most recently clicked
// Also need to kill the radioGroup button's magic logic
// Everything should be reactive off these props, so commands can inform ToolbarService
// These can... Trigger toolbar events based on updates?
// Then sync using useEffect, or simply modify the state here?
return (
// The margin for separating the tools on the toolbar should go here and NOT in each individual component (button) item.
// This allows for the individual items to be included in other UI components where perhaps alternative margins are desired.
@@ -56,9 +34,7 @@ export default function Toolbar({ servicesManager }) {
<Component
id={id}
{...componentProps}
bState={buttonState}
isActive={isActive}
onInteraction={args => toolbarService.recordInteraction(args)}
onInteraction={onInteraction}
servicesManager={servicesManager}
/>
</div>
@@ -0,0 +1,75 @@
import { ToolbarButton } from '@ohif/ui';
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
function ToolbarButtonWithServices({
id,
type,
commands,
onInteraction,
servicesManager,
...props
}) {
const { toolbarService } = servicesManager?.services || {};
const [buttonsState, setButtonState] = useState({
primaryToolId: '',
toggles: {},
groups: {},
});
const { primaryToolId } = buttonsState;
const isActive =
(type === 'tool' && id === primaryToolId) ||
(type === 'toggle' && buttonsState.toggles[id] === true);
useEffect(() => {
const { unsubscribe } = toolbarService.subscribe(
toolbarService.EVENTS.TOOL_BAR_STATE_MODIFIED,
state => {
setButtonState({ ...state });
}
);
return () => {
unsubscribe();
};
}, [toolbarService]);
return (
<ToolbarButton
commands={commands}
id={id}
type={type}
isActive={isActive}
onInteraction={onInteraction}
{...props}
/>
);
}
ToolbarButtonWithServices.propTypes = {
id: PropTypes.string.isRequired,
type: PropTypes.oneOf(['tool', 'action', 'toggle']).isRequired,
commands: PropTypes.arrayOf(
PropTypes.shape({
commandName: PropTypes.string.isRequired,
context: PropTypes.string,
})
),
onInteraction: PropTypes.func.isRequired,
servicesManager: PropTypes.shape({
services: PropTypes.shape({
toolbarService: PropTypes.shape({
subscribe: PropTypes.func.isRequired,
state: PropTypes.shape({
primaryToolId: PropTypes.string,
toggles: PropTypes.objectOf(PropTypes.bool),
groups: PropTypes.objectOf(PropTypes.object),
}).isRequired,
}).isRequired,
}).isRequired,
}).isRequired,
};
export default ToolbarButtonWithServices;
@@ -1,13 +1,37 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useState, useCallback } from 'react';
import PropTypes from 'prop-types';
import { LayoutSelector as OHIFLayoutSelector, ToolbarButton } from '@ohif/ui';
import { ServicesManager } from '@ohif/core';
function LayoutSelector({ rows, columns, className, servicesManager, ...rest }) {
const [isOpen, setIsOpen] = useState(false);
function ToolbarLayoutSelectorWithServices({ servicesManager, ...props }) {
const { toolbarService } = servicesManager.services;
const { hangingProtocolService, toolbarService } = (servicesManager as ServicesManager).services;
const onSelection = useCallback(
props => {
toolbarService.recordInteraction({
interactionType: 'action',
commands: [
{
commandName: 'setViewportGridLayout',
commandOptions: { ...props },
context: 'DEFAULT',
},
],
});
},
[toolbarService]
);
return (
<LayoutSelector
{...props}
onSelection={onSelection}
/>
);
}
function LayoutSelector({ rows, columns, className, onSelection, ...rest }) {
const [isOpen, setIsOpen] = useState(false);
const closeOnOutsideClick = () => {
if (isOpen) {
@@ -15,19 +39,6 @@ function LayoutSelector({ rows, columns, className, servicesManager, ...rest })
}
};
useEffect(() => {
const { unsubscribe } = hangingProtocolService.subscribe(
hangingProtocolService.EVENTS.PROTOCOL_CHANGED,
evt => {
const { protocol } = evt;
}
);
return () => {
unsubscribe();
};
}, [hangingProtocolService]);
useEffect(() => {
window.addEventListener('click', closeOnOutsideClick);
return () => {
@@ -38,19 +49,6 @@ function LayoutSelector({ rows, columns, className, servicesManager, ...rest })
const onInteractionHandler = () => setIsOpen(!isOpen);
const DropdownContent = isOpen ? OHIFLayoutSelector : null;
const onSelectionHandler = props => {
toolbarService.recordInteraction({
interactionType: 'action',
commands: [
{
commandName: 'setViewportGridLayout',
commandOptions: { ...props },
context: 'DEFAULT',
},
],
});
};
return (
<ToolbarButton
id="Layout"
@@ -64,7 +62,7 @@ function LayoutSelector({ rows, columns, className, servicesManager, ...rest })
<DropdownContent
rows={rows}
columns={columns}
onSelection={onSelectionHandler}
onSelection={onSelection}
/>
)
}
@@ -87,4 +85,4 @@ LayoutSelector.defaultProps = {
onLayoutChange: () => {},
};
export default LayoutSelector;
export default ToolbarLayoutSelectorWithServices;
@@ -1,3 +0,0 @@
import { SplitButton } from '@ohif/ui';
export default SplitButton;
@@ -0,0 +1,186 @@
import { SplitButton, Icon, ToolbarButton } from '@ohif/ui';
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
function ToolbarSplitButtonWithServices({
isRadio,
isAction,
groupId,
primary,
secondary,
items,
renderer,
onInteraction,
servicesManager,
}) {
const { toolbarService } = servicesManager?.services;
const handleItemClick = (item, index) => {
const { id, type, commands } = item;
onInteraction({
groupId,
itemId: id,
interactionType: type,
commands,
});
setState(state => ({
...state,
primary: !isAction && isRadio ? { ...item, index } : state.primary,
isExpanded: false,
items: getSplitButtonItems(items).filter(item =>
isRadio && !isAction ? item.index !== index : true
),
}));
};
/* Bubbles up individual item clicks */
const getSplitButtonItems = items =>
items.map((item, index) => ({
...item,
index,
onClick: () => handleItemClick(item, index),
}));
const [buttonsState, setButtonState] = useState({
primaryToolId: '',
toggles: {},
groups: {},
});
const [state, setState] = useState({
primary,
items: getSplitButtonItems(items).filter(item =>
isRadio && !isAction ? item.id !== primary.id : true
),
});
const { primaryToolId, toggles } = buttonsState;
const isPrimaryToggle = state.primary.type === 'toggle';
const isPrimaryActive =
(state.primary.type === 'tool' && primaryToolId === state.primary.id) ||
(isPrimaryToggle && toggles[state.primary.id] === true);
const PrimaryButtonComponent =
toolbarService?.getButtonComponentForUIType(state.primary.uiType) ?? ToolbarButton;
useEffect(() => {
const { unsubscribe } = toolbarService.subscribe(
toolbarService.EVENTS.TOOL_BAR_STATE_MODIFIED,
state => {
setButtonState({ ...state });
}
);
return () => {
unsubscribe();
};
}, [toolbarService]);
const updatedItems = state.items.map(item => {
const isActive = item.type === 'tool' && primaryToolId === item.id;
// We could have added the
// item.type === 'toggle' && toggles[item.id] === true
// too but that makes the button active when the toggle is active under it
// which feels weird
return {
...item,
isActive,
};
});
const DefaultListItemRenderer = ({ type, icon, label, t, id }) => {
const isActive = type === 'toggle' && toggles[id] === true;
return (
<div
className={classNames(
'hover:bg-primary-dark flex h-8 w-full flex-row items-center p-3',
'whitespace-pre text-base',
isActive && 'bg-primary-dark',
isActive
? 'text-[#348CFD]'
: 'text-common-bright hover:bg-primary-dark hover:text-primary-light'
)}
>
{icon && (
<span className="mr-4">
<Icon
name={icon}
className="h-5 w-5"
/>
</span>
)}
<span className="mr-5">{t(label)}</span>
</div>
);
};
const listItemRenderer = renderer || DefaultListItemRenderer;
return (
<SplitButton
isRadio={isRadio}
isAction={isAction}
primary={state.primary}
secondary={secondary}
items={updatedItems}
groupId={groupId}
renderer={listItemRenderer}
isActive={isPrimaryActive || updatedItems.some(item => item.isActive)}
isToggle={isPrimaryToggle}
onInteraction={onInteraction}
Component={props => (
<PrimaryButtonComponent
{...props}
servicesManager={servicesManager}
/>
)}
/>
);
}
ToolbarSplitButtonWithServices.propTypes = {
isRadio: PropTypes.bool,
isAction: PropTypes.bool,
groupId: PropTypes.string,
primary: PropTypes.shape({
id: PropTypes.string.isRequired,
type: PropTypes.oneOf(['tool', 'action', 'toggle']).isRequired,
uiType: PropTypes.string,
}),
secondary: PropTypes.shape({
id: PropTypes.string,
icon: PropTypes.string.isRequired,
label: PropTypes.string,
tooltip: PropTypes.string.isRequired,
isActive: PropTypes.bool,
}),
items: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string.isRequired,
type: PropTypes.oneOf(['tool', 'action', 'toggle']).isRequired,
icon: PropTypes.string,
label: PropTypes.string,
tooltip: PropTypes.string,
})
),
renderer: PropTypes.func,
onInteraction: PropTypes.func.isRequired,
servicesManager: PropTypes.shape({
services: PropTypes.shape({
toolbarService: PropTypes.object,
}),
}),
};
ToolbarSplitButtonWithServices.defaultProps = {
isRadio: false,
isAction: false,
};
export default ToolbarSplitButtonWithServices;
@@ -44,7 +44,7 @@ function ViewerLayout({
if (!entry) {
throw new Error(
`${id} is not a valid entry for an extension module, please check your configuration or make sure the extension is registered.`
`${id} is not valid for an extension module. Please verify your configuration or ensure that the extension is properly registered. It's also possible that your mode is utilizing a module from an extension that hasn't been included in its dependencies (add the extension to the "extensionDependencies" array in your mode's index.js file)`
);
}
+9 -9
View File
@@ -1,7 +1,7 @@
import { ToolbarButton } from '@ohif/ui';
import ToolbarDivider from './Toolbar/ToolbarDivider.tsx';
import ToolbarLayoutSelector from './Toolbar/ToolbarLayoutSelector.tsx';
import ToolbarSplitButton from './Toolbar/ToolbarSplitButton.tsx';
import ToolbarDivider from './Toolbar/ToolbarDivider';
import ToolbarLayoutSelectorWithServices from './Toolbar/ToolbarLayoutSelector';
import ToolbarSplitButtonWithServices from './Toolbar/ToolbarSplitButtonWithServices';
import ToolbarButtonWithServices from './Toolbar/ToolbarButtonWithServices';
export default function getToolbarModule({ commandsManager, servicesManager }) {
return [
@@ -12,27 +12,27 @@ export default function getToolbarModule({ commandsManager, servicesManager }) {
},
{
name: 'ohif.action',
defaultComponent: ToolbarButton,
defaultComponent: ToolbarButtonWithServices,
clickHandler: () => {},
},
{
name: 'ohif.radioGroup',
defaultComponent: ToolbarButton,
defaultComponent: ToolbarButtonWithServices,
clickHandler: () => {},
},
{
name: 'ohif.splitButton',
defaultComponent: ToolbarSplitButton,
defaultComponent: ToolbarSplitButtonWithServices,
clickHandler: () => {},
},
{
name: 'ohif.layoutSelector',
defaultComponent: ToolbarLayoutSelector,
defaultComponent: ToolbarLayoutSelectorWithServices,
clickHandler: (evt, clickedBtn, btnSectionName) => {},
},
{
name: 'ohif.toggle',
defaultComponent: ToolbarButton,
defaultComponent: ToolbarButtonWithServices,
clickHandler: () => {},
},
];
+4
View File
@@ -13,6 +13,8 @@ import { id } from './id.js';
import preRegistration from './init';
import { ContextMenuController, CustomizableContextMenuTypes } from './CustomizableContextMenu';
import * as dicomWebUtils from './DicomWebDataSource/utils';
import { createReportDialogPrompt } from './Panels';
import createReportAsync from './Actions/createReportAsync';
const defaultExtension: Types.Extensions.Extension = {
/**
@@ -48,4 +50,6 @@ export {
CustomizableContextMenuTypes,
getStudiesForPatientByMRN,
dicomWebUtils,
createReportDialogPrompt,
createReportAsync,
};