From b67b637e376ef2fb865932cbee4064ec75910808 Mon Sep 17 00:00:00 2001 From: Adithyan Dinesh Date: Wed, 10 Jul 2024 12:09:35 +0530 Subject: [PATCH] Rebase to v3.8.0 stable changes --- .../PanelMeasurementTableTracking/index.tsx | 272 ++++---- .../PanelStudyBrowser/PanelStudyBrowser.tsx | 5 +- .../PanelStudyBrowserTracking.tsx | 362 +++++----- .../PanelStudyBrowserTracking/index.tsx | 12 +- .../requestDisplaySetCreationForStudy.ts | 4 +- .../CropDisplayAreaService.ts | 89 +-- modes/breast-density-mode/src/index.tsx | 87 +-- .../breast-density-mode/src/initToolGroups.js | 113 ++-- modes/breast-density-mode/src/moreTools.ts | 190 ++++++ .../breast-density-mode/src/toolbarButtons.js | 480 ++++--------- modes/cohort/src/index.tsx | 59 +- modes/cohort/src/initToolGroups.js | 164 +++-- modes/cohort/src/moreTools.ts | 189 ++++++ modes/cohort/src/toolbarButtons.js | 640 ++++-------------- modes/cxr-mode/src/index.tsx | 97 ++- modes/cxr-mode/src/initToolGroups.js | 98 +-- modes/cxr-mode/src/moreTools.ts | 187 +++++ modes/cxr-mode/src/toolbarButtons.js | 558 ++++----------- 18 files changed, 1666 insertions(+), 1940 deletions(-) create mode 100644 modes/breast-density-mode/src/moreTools.ts create mode 100644 modes/cohort/src/moreTools.ts create mode 100644 modes/cxr-mode/src/moreTools.ts diff --git a/extensions/ohif-gradienthealth-extension/src/panels/PanelMeasurementTableTracking/index.tsx b/extensions/ohif-gradienthealth-extension/src/panels/PanelMeasurementTableTracking/index.tsx index 1a25a11..482042f 100644 --- a/extensions/ohif-gradienthealth-extension/src/panels/PanelMeasurementTableTracking/index.tsx +++ b/extensions/ohif-gradienthealth-extension/src/panels/PanelMeasurementTableTracking/index.tsx @@ -1,47 +1,30 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useRef } from 'react'; import PropTypes from 'prop-types'; import { StudySummary, MeasurementTable, - Dialog, - Input, useViewportGrid, + ActionButtons } from '@ohif/ui'; import { DicomMetadataStore, utils } from '@ohif/core'; - -function useDebounce(value, delay) { - // State and setters for debounced value - const [debouncedValue, setDebouncedValue] = useState(value); - useEffect( - () => { - // Update debounced value after delay - const handler = setTimeout(() => { - setDebouncedValue(value); - }, delay); - // Cancel the timeout if value changes (also on delay change or unmount) - // This is how we prevent debounced value from updating if value is changed ... - // .. within the delay period. Timeout gets cleared and restarted. - return () => { - clearTimeout(handler); - }; - }, - [value, delay] // Only re-call effect if value or delay changes - ); - return debouncedValue; -} +import { useDebounce } from '@hooks'; +import { useAppConfig } from '@state'; +import debounce from 'lodash.debounce'; +import { useTranslation } from 'react-i18next'; const { downloadCSVReport } = utils; const { formatDate } = utils; const DISPLAY_STUDY_SUMMARY_INITIAL_VALUE = { key: undefined, // - date: undefined, // '07-Sep-2010', - modality: undefined, // 'CT', - description: undefined, // 'CHEST/ABD/PELVIS W CONTRAST', + date: '', // '07-Sep-2010', + modality: '', // 'CT', + description: '', // 'CHEST/ABD/PELVIS W CONTRAST', }; function PanelMeasurementTableTracking({ servicesManager, extensionManager }) { const [viewportGrid, viewportGridService] = useViewportGrid(); + const { t } = useTranslation('MeasurementTable'); const [measurementChangeTimestamp, setMeasurementsUpdated] = useState( Date.now().toString() ); @@ -49,41 +32,34 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) { measurementChangeTimestamp, 200 ); - const { - MeasurementService, - UIDialogService, - DisplaySetService, - } = servicesManager.services; + const { measurementService, uiDialogService, displaySetService, customizationService } = + servicesManager.services; const [displayStudySummary, setDisplayStudySummary] = useState( DISPLAY_STUDY_SUMMARY_INITIAL_VALUE ); const [displayMeasurements, setDisplayMeasurements] = useState([]); + const measurementsPanelRef = useRef(null); + const [appConfig] = useAppConfig(); useEffect(() => { - const measurements = MeasurementService.getMeasurements(); + const measurements = measurementService.getMeasurements(); - const mappedMeasurements = measurements.map(m => + const mappedMeasurements = measurements.map((m) => _mapMeasurementToDisplay( m, - MeasurementService.VALUE_TYPES, - DisplaySetService + measurementService.VALUE_TYPES, + displaySetService ) ); setDisplayMeasurements(mappedMeasurements); // eslint-ignore-next-line - }, [ - MeasurementService, - debouncedMeasurementChangeTimestamp, - ]); - + }, [measurementService, debouncedMeasurementChangeTimestamp]); // ~~ DisplayStudySummary useEffect(() => { setDisplayStudySummary(DISPLAY_STUDY_SUMMARY_INITIAL_VALUE); - }, [ - displayStudySummary.key, - ]); + }, [displayStudySummary.key]); // TODO: Better way to consolidated, debounce, check on change? // Are we exposing the right API for measurementService? @@ -91,130 +67,92 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) { // which is debounced. After a brief period of inactivity, this triggers // a re-render where we grab up-to-date measurements useEffect(() => { - const added = MeasurementService.EVENTS.MEASUREMENT_ADDED; - const addedRaw = MeasurementService.EVENTS.RAW_MEASUREMENT_ADDED; - const updated = MeasurementService.EVENTS.MEASUREMENT_UPDATED; - const removed = MeasurementService.EVENTS.MEASUREMENT_REMOVED; - const cleared = MeasurementService.EVENTS.MEASUREMENTS_CLEARED; - const subscriptions = []; - - [added, addedRaw, updated, removed, cleared].forEach(evt => { + const added = measurementService.EVENTS.MEASUREMENT_ADDED; + const addedRaw = measurementService.EVENTS.RAW_MEASUREMENT_ADDED; + const updated = measurementService.EVENTS.MEASUREMENT_UPDATED; + const removed = measurementService.EVENTS.MEASUREMENT_REMOVED; + const cleared = measurementService.EVENTS.MEASUREMENTS_CLEARED; + const subscriptions:any[] = []; + + [added, addedRaw, updated, removed, cleared].forEach((evt) => { subscriptions.push( - MeasurementService.subscribe(evt, () => { + measurementService.subscribe(evt, () => { setMeasurementsUpdated(Date.now().toString()); + if (evt === added) { + debounce(() => { + measurementsPanelRef.current.scrollTop = measurementsPanelRef.current.scrollHeight; + }, 300)(); + } }).unsubscribe ); }); return () => { - subscriptions.forEach(unsub => { + subscriptions.forEach((unsub) => { unsub(); }); }; - }, [MeasurementService]); + }, [measurementService]); async function exportReport() { - const measurements = MeasurementService.getMeasurements(); - downloadCSVReport(measurements, MeasurementService); + const measurements = measurementService.getMeasurements(); + downloadCSVReport(measurements, measurementService); } const jumpToImage = ({ uid, isActive }) => { - MeasurementService.jumpToMeasurement(viewportGrid.activeViewportId, uid); + measurementService.jumpToMeasurement(viewportGrid.activeViewportId, uid); onMeasurementItemClickHandler({ uid, isActive }); }; const onMeasurementItemEditHandler = ({ uid, isActive }) => { - const measurement = MeasurementService.getMeasurement(uid); jumpToImage({ uid, isActive }); - const onSubmitHandler = ({ action, value }) => { - switch (action.id) { - case 'save': { - MeasurementService.update( - uid, - { - ...measurement, - ...value, - }, - true - ); - } + const labelConfig = customizationService.get('measurementLabels'); + const measurement = measurementService.getMeasurement(uid); + const utilityModule = extensionManager.getModuleEntry( + '@ohif/extension-cornerstone.utilityModule.common' + ); + const { showLabelAnnotationPopup } = utilityModule.exports; + showLabelAnnotationPopup(measurement, uiDialogService, labelConfig).then( + (val: Map) => { + measurementService.update( + uid, + { + ...val, + }, + true + ); } - UIDialogService.dismiss({ id: 'enter-annotation' }); - }; - - UIDialogService.create({ - id: 'enter-annotation', - centralize: true, - isDraggable: false, - showOverlay: true, - content: Dialog, - contentProps: { - title: 'Enter your annotation', - noCloseButton: true, - value: { label: measurement.label || '' }, - body: ({ value, setValue }) => { - const onChangeHandler = event => { - event.persist(); - setValue(value => ({ ...value, label: event.target.value })); - }; - - const onKeyPressHandler = event => { - if (event.key === 'Enter') { - onSubmitHandler({ value, action: { id: 'save' } }); - } - }; - return ( -
- -
- ); - }, - actions: [ - // temp: swap button types until colors are updated - { id: 'cancel', text: 'Cancel', type: 'primary' }, - { id: 'save', text: 'Save', type: 'secondary' }, - ], - onSubmit: onSubmitHandler, - }, - }); + ); }; const onMeasurementItemClickHandler = ({ uid, isActive }) => { if (!isActive) { const measurements = [...displayMeasurements]; - const measurement = measurements.find(m => m.uid === uid); + const measurement = measurements.find((m) => m.uid === uid); - measurements.forEach(m => (m.isActive = m.uid !== uid ? false : true)); + measurements.forEach((m) => (m.isActive = m.uid !== uid ? false : true)); measurement.isActive = true; setDisplayMeasurements(measurements); } }; - const onMeasurementDeleteHandler = ({ uid }) => { - MeasurementService.remove(uid) - } - const displayMeasurementsWithoutFindings = displayMeasurements.filter( - dm => dm.measurementType !== MeasurementService.VALUE_TYPES.POINT + (dm) => dm.measurementType !== measurementService.VALUE_TYPES.POINT ); const additionalFindings = displayMeasurements.filter( - dm => dm.measurementType === MeasurementService.VALUE_TYPES.POINT + (dm) => dm.measurementType === measurementService.VALUE_TYPES.POINT ); + const disabled = + additionalFindings.length === 0 && displayMeasurementsWithoutFindings.length === 0; + return ( <>
{displayStudySummary.key && ( @@ -228,20 +166,44 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) { title="Measurements" amount={displayMeasurementsWithoutFindings.length} data={displayMeasurementsWithoutFindings} + servicesManager={servicesManager} onClick={jumpToImage} onEdit={onMeasurementItemEditHandler} - onDelete={onMeasurementDeleteHandler} /> {additionalFindings.length !== 0 && ( + servicesManager={servicesManager} + onClick={jumpToImage} + onEdit={onMeasurementItemEditHandler} + /> )}
+ {!appConfig?.disableEditing && ( +
+ { + sendTrackedMeasurementsEvent('SAVE_REPORT', { + viewportId: viewportGrid.activeViewportId, + isBackupSave: true, + }); + }, + }, + ]} + disabled={disabled} + /> +
+ )} ); } @@ -249,7 +211,7 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) { PanelMeasurementTableTracking.propTypes = { servicesManager: PropTypes.shape({ services: PropTypes.shape({ - MeasurementService: PropTypes.shape({ + measurementService: PropTypes.shape({ getMeasurements: PropTypes.func.isRequired, VALUE_TYPES: PropTypes.object.isRequired, }).isRequired, @@ -258,7 +220,7 @@ PanelMeasurementTableTracking.propTypes = { }; // TODO: This could be a MeasurementService mapper -function _mapMeasurementToDisplay(measurement, types, DisplaySetService) { +function _mapMeasurementToDisplay(measurement, types, displaySetService) { const { referenceStudyUID, referenceSeriesUID, SOPInstanceUID } = measurement; // TODO: We don't deal with multiframe well yet, would need to update @@ -270,9 +232,8 @@ function _mapMeasurementToDisplay(measurement, types, DisplaySetService) { SOPInstanceUID ); - const displaySets = DisplaySetService.getDisplaySetsForSeries( - referenceSeriesUID - ); + const displaySets = + displaySetService.getDisplaySetsForSeries(referenceSeriesUID); if (!displaySets[0] || !displaySets[0].images) { throw new Error( @@ -280,13 +241,42 @@ function _mapMeasurementToDisplay(measurement, types, DisplaySetService) { ); } - const { displayText } = measurement; + const { + displayText: baseDisplayText, + uid, + label: baseLabel, + type, + selected, + findingSites, + finding, + } = measurement; + + const firstSite = findingSites?.[0]; + const label = baseLabel || finding?.text || firstSite?.text || '(empty)'; + let displayText = baseDisplayText || []; + if (findingSites) { + const siteText = []; + findingSites.forEach(site => { + if (site?.text !== label) { + siteText.push(site.text); + } + }); + displayText = [...siteText, ...displayText]; + } + if (finding && finding?.text !== label) { + displayText = [finding.text, ...displayText]; + } + return { - uid: measurement.uid, - label: measurement.label || '(empty)', - measurementType: measurement.type, - displayText: displayText || [], - isActive: false, // activeMeasurementItem === i + 1, + uid, + label, + baseLabel, + measurementType: type, + displayText, + baseDisplayText, + isActive: selected, + finding, + findingSites, }; } diff --git a/extensions/ohif-gradienthealth-extension/src/panels/PanelStudyBrowser/PanelStudyBrowser.tsx b/extensions/ohif-gradienthealth-extension/src/panels/PanelStudyBrowser/PanelStudyBrowser.tsx index 7ba9aa7..26afd3b 100644 --- a/extensions/ohif-gradienthealth-extension/src/panels/PanelStudyBrowser/PanelStudyBrowser.tsx +++ b/extensions/ohif-gradienthealth-extension/src/panels/PanelStudyBrowser/PanelStudyBrowser.tsx @@ -26,7 +26,7 @@ function PanelStudyBrowser({ // Tabs --> Studies --> DisplaySets --> Thumbnails const { StudyInstanceUIDs } = useImageViewer(); const [studyInstanceUIDs, setStudyInstanceUIDs] = useState([...StudyInstanceUIDs]); - const [{ activeViewportId, viewports }, viewportGridService] = useViewportGrid(); + const [{ activeViewportId, viewports, isHangingProtocolLayout }, viewportGridService] = useViewportGrid(); const [activeTabName, setActiveTabName] = useState('primary'); const [expandedStudyInstanceUIDs, setExpandedStudyInstanceUIDs] = useState([ ...studyInstanceUIDs, @@ -41,7 +41,8 @@ function PanelStudyBrowser({ try { updatedViewports = hangingProtocolService.getViewportsRequireUpdate( viewportId, - displaySetInstanceUID + displaySetInstanceUID, + isHangingProtocolLayout ); } catch (error) { console.warn(error); diff --git a/extensions/ohif-gradienthealth-extension/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.tsx b/extensions/ohif-gradienthealth-extension/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.tsx index 301c25c..2c4aecc 100644 --- a/extensions/ohif-gradienthealth-extension/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.tsx +++ b/extensions/ohif-gradienthealth-extension/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.tsx @@ -1,13 +1,9 @@ import React, { useState, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; import PropTypes from 'prop-types'; import { utils } from '@ohif/core'; -import { - StudyBrowser, - useImageViewer, - useViewportGrid, - Dialog, -} from '@ohif/ui'; -import { useNavigate } from 'react-router-dom'; +import { StudyBrowser, useImageViewer, useViewportGrid, Dialog, ButtonEnums } from '@ohif/ui'; const { formatDate } = utils; @@ -16,25 +12,30 @@ const { formatDate } = utils; * @param {*} param0 */ function PanelStudyBrowserTracking({ - MeasurementService, - DisplaySetService, - UIDialogService, - UINotificationService, + servicesManager, getImageSrc, getStudiesForPatientByMRN, requestDisplaySetCreationForStudy, dataSource, }) { + const { + displaySetService, + uiDialogService, + hangingProtocolService, + uiNotificationService, + } = servicesManager.services; + const navigate = useNavigate(); + + const { t } = useTranslation('Common'); + // Normally you nest the components so the tree isn't so deep, and the data // doesn't have to have such an intense shape. This works well enough for now. // Tabs --> Studies --> DisplaySets --> Thumbnails const { StudyInstanceUIDs } = useImageViewer(); const [ - { activeViewportId, viewports, numCols, numRows }, + { activeViewportId, viewports, isHangingProtocolLayout }, viewportGridService, ] = useViewportGrid(); - const navigate=useNavigate() - const [activeTabName, setActiveTabName] = useState('primary'); const [expandedStudyInstanceUIDs, setExpandedStudyInstanceUIDs] = useState([ ...StudyInstanceUIDs, @@ -45,40 +46,31 @@ function PanelStudyBrowserTracking({ const [jumpToDisplaySet, setJumpToDisplaySet] = useState(null); const onDoubleClickThumbnailHandler = (displaySetInstanceUID) => { - viewportGridService.setDisplaySetsForViewport({ - viewportId: activeViewportId, - displaySetInstanceUIDs: [displaySetInstanceUID], - }); + let updatedViewports = []; + const viewportId = activeViewportId; + try { + updatedViewports = hangingProtocolService.getViewportsRequireUpdate( + viewportId, + displaySetInstanceUID, + isHangingProtocolLayout + ); + } catch (error) { + console.warn(error); + uiNotificationService.show({ + title: 'Thumbnail Double Click', + message: + 'The selected display sets could not be added to the viewport due to a mismatch in the Hanging Protocol rules.', + type: 'info', + duration: 3000, + }); + } + + viewportGridService.setDisplaySetsForViewports(updatedViewports); }; const activeViewportDisplaySetInstanceUIDs = viewports.get(activeViewportId)?.displaySetInstanceUIDs; - const isSingleViewport = numCols === 1 && numRows === 1; - - useEffect(() => { - const added = MeasurementService.EVENTS.MEASUREMENT_ADDED; - const addedRaw = MeasurementService.EVENTS.RAW_MEASUREMENT_ADDED; - const subscriptions = []; - - [added, addedRaw].forEach((evt) => { - subscriptions.push( - MeasurementService.subscribe(evt, ({ source, measurement }) => { - const { - referenceSeriesUID: SeriesInstanceUID, - referenceStudyUID: StudyInstanceUID, - } = measurement; - }).unsubscribe - ); - }); - - return () => { - subscriptions.forEach((unsub) => { - unsub(); - }); - }; - }, [MeasurementService, activeViewportId]); - // ~~ studyDisplayList useEffect(() => { // Fetch all studies for the patient in each primary study @@ -107,7 +99,7 @@ function PanelStudyBrowserTracking({ const actuallyMappedStudies = mappedStudies.map(qidoStudy => { return { studyInstanceUid: qidoStudy.StudyInstanceUID, - date: formatDate(qidoStudy.StudyDate), + date: formatDate(qidoStudy.StudyDate) || t('NoStudyDate'), description: qidoStudy.StudyDescription, modalities: qidoStudy.ModalitiesInStudy, numInstances: qidoStudy.NumInstances, @@ -126,107 +118,137 @@ function PanelStudyBrowserTracking({ } StudyInstanceUIDs.forEach(sid => fetchStudiesForPatient(sid)); - }, [StudyInstanceUIDs, dataSource, getStudiesForPatientByMRN, navigate]); + }, [StudyInstanceUIDs, dataSource, getStudiesForPatientByMRN]); // ~~ Initial Thumbnails useEffect(() => { - const currentDisplaySets = DisplaySetService.activeDisplaySets; + const currentDisplaySets = displaySetService.activeDisplaySets; + + if (!currentDisplaySets.length) { + return; + } + currentDisplaySets.forEach(async dSet => { const newImageSrcEntry = {}; - const displaySet = DisplaySetService.getDisplaySetByUID( + const displaySet = displaySetService.getDisplaySetByUID( dSet.displaySetInstanceUID ); const imageIds = dataSource.getImageIdsForDisplaySet(displaySet); - const imageId = imageIds[Math.floor(imageIds.length / 2)]; + const imageId = getImageIdForThumbnail(displaySet,imageIds) // 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 - ); - 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); + setThumbnailImageSrcMap((prevState) => { + return { ...prevState, ...newImageSrcEntry }; + }); + }); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [DisplaySetService, dataSource, getImageSrc]); + }, [displaySetService, dataSource, getImageSrc]); // ~~ displaySets useEffect(() => { // TODO: Are we sure `activeDisplaySets` will always be accurate? - const currentDisplaySets = DisplaySetService.activeDisplaySets; + const currentDisplaySets = displaySetService.activeDisplaySets; + + if (!currentDisplaySets.length) { + return; + } + const mappedDisplaySets = _mapDisplaySets( currentDisplaySets, thumbnailImageSrcMap, - viewports, - isSingleViewport, dataSource, - DisplaySetService, - UIDialogService, - UINotificationService + displaySetService, + uiDialogService, + uiNotificationService ); setDisplaySets(mappedDisplaySets); // eslint-disable-next-line react-hooks/exhaustive-deps }, [ - DisplaySetService.activeDisplaySets, - thumbnailImageSrcMap, + displaySetService.activeDisplaySets, viewports, dataSource, + thumbnailImageSrcMap, ]); // ~~ subscriptions --> displaySets useEffect(() => { // DISPLAY_SETS_ADDED returns an array of DisplaySets that were added - const SubscriptionDisplaySetsAdded = DisplaySetService.subscribe( - DisplaySetService.EVENTS.DISPLAY_SETS_ADDED, + const SubscriptionDisplaySetsAdded = displaySetService.subscribe( + displaySetService.EVENTS.DISPLAY_SETS_ADDED, data => { const { displaySetsAdded, options } = data; displaySetsAdded.forEach(async dSet => { const displaySetInstanceUID = dSet.displaySetInstanceUID; const newImageSrcEntry = {}; - const displaySet = DisplaySetService.getDisplaySetByUID( - displaySetInstanceUID - ); + const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID); + if (displaySet?.unsupported) { + return; + } if (options.madeInClient) { setJumpToDisplaySet(displaySetInstanceUID); } const imageIds = dataSource.getImageIdsForDisplaySet(displaySet); - const imageId = imageIds[Math.floor(imageIds.length / 2)]; + const imageId = getImageIdForThumbnail(displaySet, imageIds); // 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 (!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, viewports]); + + useEffect(() => { // TODO: Will this always hold _all_ the displaySets we care about? // DISPLAY_SETS_CHANGED returns `DisplaySerService.activeDisplaySets` - const SubscriptionDisplaySetsChanged = DisplaySetService.subscribe( - DisplaySetService.EVENTS.DISPLAY_SETS_CHANGED, + const SubscriptionDisplaySetsChanged = displaySetService.subscribe( + displaySetService.EVENTS.DISPLAY_SETS_CHANGED, changedDisplaySets => { const mappedDisplaySets = _mapDisplaySets( changedDisplaySets, thumbnailImageSrcMap, - viewports, - isSingleViewport, dataSource, - DisplaySetService, - UIDialogService, - UINotificationService + displaySetService, + uiDialogService, + uiNotificationService + ); + + setDisplaySets(mappedDisplaySets); + } + ); + + const SubscriptionDisplaySetMetaDataInvalidated = displaySetService.subscribe( + displaySetService.EVENTS.DISPLAY_SET_SERIES_METADATA_INVALIDATED, + () => { + const mappedDisplaySets = _mapDisplaySets( + displaySetService.getActiveDisplaySets(), + thumbnailImageSrcMap, + dataSource, + displaySetService, + uiDialogService, + uiNotificationService ); setDisplaySets(mappedDisplaySets); @@ -234,34 +256,24 @@ function PanelStudyBrowserTracking({ ); return () => { - SubscriptionDisplaySetsAdded.unsubscribe(); SubscriptionDisplaySetsChanged.unsubscribe(); + SubscriptionDisplaySetMetaDataInvalidated.unsubscribe(); }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ - DisplaySetService, - dataSource, - getImageSrc, - thumbnailImageSrcMap, - viewports, - ]); + }, [thumbnailImageSrcMap, viewports, dataSource, displaySetService]); const tabs = _createStudyBrowserTabs( StudyInstanceUIDs, studyDisplayList, - displaySets + displaySets, + hangingProtocolService ); // TODO: Should not fire this on "close" function _handleStudyClick(StudyInstanceUID) { - const shouldCollapseStudy = expandedStudyInstanceUIDs.includes( - StudyInstanceUID - ); + const shouldCollapseStudy = expandedStudyInstanceUIDs.includes(StudyInstanceUID); const updatedExpandedStudyInstanceUIDs = shouldCollapseStudy ? [ - ...expandedStudyInstanceUIDs.filter( - stdyUid => stdyUid !== StudyInstanceUID - ), + ...expandedStudyInstanceUIDs.filter(stdyUid => stdyUid !== StudyInstanceUID), ] : [...expandedStudyInstanceUIDs, StudyInstanceUID]; @@ -269,11 +281,7 @@ function PanelStudyBrowserTracking({ if (!shouldCollapseStudy) { const madeInClient = true; - requestDisplaySetCreationForStudy( - DisplaySetService, - StudyInstanceUID, - madeInClient - ); + requestDisplaySetCreationForStudy(displaySetService,StudyInstanceUID,madeInClient); } } @@ -281,9 +289,7 @@ function PanelStudyBrowserTracking({ if (jumpToDisplaySet) { // Get element by displaySetInstanceUID const displaySetInstanceUID = jumpToDisplaySet; - const element = document.getElementById( - `thumbnail-${displaySetInstanceUID}` - ); + const element = document.getElementById(`thumbnail-${displaySetInstanceUID}`); if (element && typeof element.scrollIntoView === 'function') { // TODO: Any way to support IE here? @@ -301,10 +307,7 @@ function PanelStudyBrowserTracking({ const displaySetInstanceUID = jumpToDisplaySet; // Set the activeTabName and expand the study - const thumbnailLocation = _findTabAndStudyOfDisplaySet( - displaySetInstanceUID, - tabs - ); + const thumbnailLocation = _findTabAndStudyOfDisplaySet(displaySetInstanceUID,tabs); if (!thumbnailLocation) { console.warn('jumpToThumbnail: displaySet thumbnail not found.'); @@ -325,6 +328,7 @@ function PanelStudyBrowserTracking({ return ( { + displaySets + .filter(ds => !ds.excludeFromThumbnailBrowser) + .forEach(ds => { const imageSrc = thumbnailImageSrcMap[ds.displaySetInstanceUID]; - const componentType = _getComponentType(ds.Modality); - const viewportIdentificator = isSingleViewport - ? [] - : Object.values(viewports).reduce((acc, viewportData, index) => { - if ( - viewportData?.displaySetInstanceUIDs?.includes( - ds.displaySetInstanceUID - ) - ) { - acc.push(viewportData.viewportLabel); - } - return acc; - }, []); + const componentType = _getComponentType(ds); const array = - componentType === 'thumbnailTracked' - ? thumbnailDisplaySets - : thumbnailNoImageDisplaySets; + componentType === 'thumbnailTracked' ? thumbnailDisplaySets : thumbnailNoImageDisplaySets; const { displaySetInstanceUID } = ds; const thumbnailProps = { displaySetInstanceUID, description: ds.SeriesDescription, - seriesNumber: String(ds.SeriesNumber), + seriesNumber: ds.SeriesNumber, modality: ds.Modality, seriesDate: formatDate(ds.SeriesDate), numInstances: ds.numImageFrames, + countIcon: ds.countIcon, + messages: ds.messages, StudyInstanceUID: ds.StudyInstanceUID, componentType, imageSrc, @@ -431,14 +428,14 @@ function _mapDisplaySets( displaySetInstanceUID, // .. Any other data to pass }, - viewportIdentificator, + isHydratedForDerivedDisplaySet: ds.isHydrated, }; if (componentType === 'thumbnailNoImage') { if (dataSource.reject && dataSource.reject.series) { - thumbnailProps.canReject = true; + thumbnailProps.canReject = !ds?.unsupported; thumbnailProps.onReject = () => { - UIDialogService.create({ + uiDialogService.create({ id: 'ds-reject-sr', centralize: true, isDraggable: false, @@ -447,21 +444,25 @@ function _mapDisplaySets( contentProps: { title: 'Delete Report', body: () => ( -
+

Are you sure you want to delete this report?

-

This action cannot be undone.

+

This action cannot be undone.

), actions: [ - { id: 'cancel', text: 'Cancel', type: 'secondary' }, + { + id: 'cancel', + text: 'Cancel', + type: ButtonEnums.type.secondary, + }, { id: 'yes', text: 'Yes', - type: 'primary', + type: ButtonEnums.type.primary, classes: ['reject-yes-button'], }, ], - onClose: () => UIDialogService.dismiss({ id: 'ds-reject-sr' }), + onClose: () => uiDialogService.dismiss({ id: 'ds-reject-sr' }), onShow: () => { const yesButton = document.querySelector('.reject-yes-button'); @@ -471,20 +472,17 @@ function _mapDisplaySets( switch (action.id) { case 'yes': try { - await dataSource.reject.series( - ds.StudyInstanceUID, - ds.SeriesInstanceUID - ); - DisplaySetService.deleteDisplaySet(displaySetInstanceUID); - UIDialogService.dismiss({ id: 'ds-reject-sr' }); - UINotificationService.show({ + await dataSource.reject.series(ds.StudyInstanceUID, ds.SeriesInstanceUID); + displaySetService.deleteDisplaySet(displaySetInstanceUID); + uiDialogService.dismiss({ id: 'ds-reject-sr' }); + uiNotificationService.show({ title: 'Delete Report', message: 'Report deleted successfully', type: 'success', }); } catch (error) { - UIDialogService.dismiss({ id: 'ds-reject-sr' }); - UINotificationService.show({ + uiDialogService.dismiss({ id: 'ds-reject-sr' }); + uiNotificationService.show({ title: 'Delete Report', message: 'Failed to delete report', type: 'error', @@ -492,7 +490,7 @@ function _mapDisplaySets( } break; case 'cancel': - UIDialogService.dismiss({ id: 'ds-reject-sr' }); + uiDialogService.dismiss({ id: 'ds-reject-sr' }); break; } }, @@ -510,24 +508,16 @@ function _mapDisplaySets( return [...thumbnailDisplaySets, ...thumbnailNoImageDisplaySets]; } -const thumbnailNoImageModalities = [ - 'SR', - 'SEG', - 'RTSTRUCT', - 'RTPLAN', - 'RTDOSE', -]; +const thumbnailNoImageModalities = ['SR', 'SEG', 'SM', 'RTSTRUCT', 'RTPLAN', 'RTDOSE', 'DOC', 'OT']; -function _getComponentType(Modality) { - if (thumbnailNoImageModalities.includes(Modality)) { +function _getComponentType(ds) { + if (thumbnailNoImageModalities.includes(ds.Modality) || ds?.unsupported) { return 'thumbnailNoImage'; } return 'thumbnailTracked'; } -const _viewportLabels = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I']; - /** * * @param {string[]} primaryStudyInstanceUIDs @@ -543,7 +533,8 @@ const _viewportLabels = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I']; function _createStudyBrowserTabs( primaryStudyInstanceUIDs, studyDisplayList, - displaySets + displaySets, + hangingProtocolService ) { const primaryStudies = []; const recentStudies = []; @@ -557,9 +548,8 @@ function _createStudyBrowserTabs( ); // Sort them - const sortedDisplaySetsForStudy = utils.sortBySeriesDate( - displaySetsForStudy - ); + const dsSortFn = hangingProtocolService.getDisplaySetSortFunction(); + displaySetsForStudy.sort(dsSortFn); /* Sort by series number, then by series date displaySetsForStudy.sort((a, b) => { @@ -602,23 +592,17 @@ function _createStudyBrowserTabs( { name: 'primary', label: 'Primary', - studies: primaryStudies.sort((studyA, studyB) => - _byDate(studyA.date, studyB.date) - ), + studies: primaryStudies.sort((studyA, studyB) => _byDate(studyA.date, studyB.date)), }, { name: 'recent', label: 'Recent', - studies: recentStudies.sort((studyA, studyB) => - _byDate(studyA.date, studyB.date) - ), + studies: recentStudies.sort((studyA, studyB) => _byDate(studyA.date, studyB.date)), }, { name: 'all', label: 'All', - studies: allStudies.sort((studyA, studyB) => - _byDate(studyA.date, studyB.date) - ), + studies: allStudies.sort((studyA, studyB) => _byDate(studyA.date, studyB.date)), }, ]; diff --git a/extensions/ohif-gradienthealth-extension/src/panels/PanelStudyBrowserTracking/index.tsx b/extensions/ohif-gradienthealth-extension/src/panels/PanelStudyBrowserTracking/index.tsx index b480f53..f28f750 100644 --- a/extensions/ohif-gradienthealth-extension/src/panels/PanelStudyBrowserTracking/index.tsx +++ b/extensions/ohif-gradienthealth-extension/src/panels/PanelStudyBrowserTracking/index.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useCallback } from 'react'; import PropTypes from 'prop-types'; import PanelStudyBrowserTracking from './PanelStudyBrowserTracking'; @@ -28,17 +28,15 @@ function WrappedPanelStudyBrowserTracking({ extensionManager, dataSource ); - const _getImageSrcFromImageId = - createGetImageSrcFromImageIdFn(extensionManager); + const _getImageSrcFromImageId = useCallback( + createGetImageSrcFromImageIdFn(extensionManager) + ); const _requestDisplaySetCreationForStudy = createRequestDisplaySetcreationFn(dataSource); return ( displaySet.StudyInstanceUID === StudyInstanceUID ) ) { diff --git a/extensions/ohif-gradienthealth-extension/src/services/CropDisplayAreaService/CropDisplayAreaService.ts b/extensions/ohif-gradienthealth-extension/src/services/CropDisplayAreaService/CropDisplayAreaService.ts index 46003d6..d7f5a59 100644 --- a/extensions/ohif-gradienthealth-extension/src/services/CropDisplayAreaService/CropDisplayAreaService.ts +++ b/extensions/ohif-gradienthealth-extension/src/services/CropDisplayAreaService/CropDisplayAreaService.ts @@ -295,15 +295,12 @@ export default class CropDisplayAreaService { }); if (!viewportsWithSegmentation.length) { - const activeViewport = - cornerstoneViewportService.getCornerstoneViewport(activeViewportId); - handleFocusingForNewStack( - activeViewport, displaySetService, zoomFactors, imagePoint, - imageAspectRatio + imageAspectRatio, + cornerstoneViewportService ); } } @@ -322,53 +319,67 @@ const setDisplayArea = ( }; const handleFocusingForNewStack = ( - viewport: IStackViewport | IVolumeViewport, displaySetService: any, zoomFactors: { x: number; y: number }, imagePoint: [number, number], - imageAspectRatio: number + imageAspectRatio: number, + cornerstoneViewportService ) => { - const canvasAspectRatio = viewport.sWidth / viewport.sHeight; - - const eventElement = + + const elementEnabledListener = (evt) => { + const viewport = cornerstoneViewportService.getCornerstoneViewport(evt.detail.viewportId) + const eventElement = viewport.type === CSCORE_ENUMS.ViewportType.STACK - ? CornerstoneEventTarget - : viewport.element; - const eventName = + ? CornerstoneEventTarget + : viewport.element; + const eventName = viewport.type === CSCORE_ENUMS.ViewportType.STACK - ? CS_EVENTS.STACK_VIEWPORT_NEW_STACK - : CS_EVENTS.VOLUME_VIEWPORT_NEW_VOLUME; + ? CS_EVENTS.STACK_VIEWPORT_NEW_STACK + : CS_EVENTS.VOLUME_VIEWPORT_NEW_VOLUME; - const newImageListener = (evt) => { - const segDisplaySetsOfLoadedSeries = getSegDisplaysetsOfReferencedImagesIds( - evt.detail.imageIds, - displaySetService - ); - - let segmentationsRenderedCount = 0; - const segmentationRenderedListener = () => { - if ( - ++segmentationsRenderedCount === segDisplaySetsOfLoadedSeries.length - ) { - correctZoomFactors(zoomFactors, imageAspectRatio, canvasAspectRatio); - setDisplayArea(viewport, zoomFactors, imagePoint); - - CornerstoneEventTarget.removeEventListener( - CSTOOLS_ENUMS.Events.SEGMENTATION_RENDERED, - segmentationRenderedListener + const canvasAspectRatio = viewport.sWidth / viewport.sHeight; + + const newImageListener = (evt) => { + const segDisplaySetsOfLoadedSeries = + getSegDisplaysetsOfReferencedImagesIds( + evt.detail.imageIds, + displaySetService ); - } + + let segmentationsRenderedCount = 0; + const segmentationRenderedListener = () => { + if ( + ++segmentationsRenderedCount === segDisplaySetsOfLoadedSeries.length + ) { + correctZoomFactors(zoomFactors, imageAspectRatio, canvasAspectRatio); + setDisplayArea(viewport, zoomFactors, imagePoint); + + CornerstoneEventTarget.removeEventListener( + CSTOOLS_ENUMS.Events.SEGMENTATION_RENDERED, + segmentationRenderedListener + ); + } + }; + + CornerstoneEventTarget.addEventListener( + CSTOOLS_ENUMS.Events.SEGMENTATION_RENDERED, + segmentationRenderedListener + ); + + eventElement.removeEventListener(eventName, newImageListener); }; - CornerstoneEventTarget.addEventListener( - CSTOOLS_ENUMS.Events.SEGMENTATION_RENDERED, - segmentationRenderedListener + eventElement.addEventListener(eventName, newImageListener); + CornerstoneEventTarget.removeEventListener( + CSCORE_ENUMS.Events.ELEMENT_ENABLED, + elementEnabledListener ); - - eventElement.removeEventListener(eventName, newImageListener); }; - eventElement.addEventListener(eventName, newImageListener); + CornerstoneEventTarget.addEventListener( + CSCORE_ENUMS.Events.ELEMENT_ENABLED, + elementEnabledListener + ); }; const correctZoomFactors = ( diff --git a/modes/breast-density-mode/src/index.tsx b/modes/breast-density-mode/src/index.tsx index f5a75cd..d26f456 100644 --- a/modes/breast-density-mode/src/index.tsx +++ b/modes/breast-density-mode/src/index.tsx @@ -1,5 +1,6 @@ import hotkeys from './hotkeyBindings.js'; import toolbarButtons from './toolbarButtons.js'; +import moreTools from './moreTools.ts'; import { id } from './id.js'; import initToolGroups from './initToolGroups.js'; @@ -46,6 +47,7 @@ const extensionDependencies = { }; function modeFactory({ modeConfiguration }) { + let _activatePanelTriggersSubscriptions = []; return { // TODO: We're using this as a route segment // We should not be. @@ -57,53 +59,24 @@ function modeFactory({ modeConfiguration }) { */ onModeEnter: ({ servicesManager, extensionManager, commandsManager }) => { const { - ToolBarService, - ToolGroupService, + toolbarService, + toolGroupService, GoogleSheetsService, CropDisplayAreaService, CacheAPIService, + measurementService } = servicesManager.services; - GoogleSheetsService.init(); - CropDisplayAreaService.init(); - CacheAPIService.init(); + measurementService.clearMeasurements(); // Init Default and SR ToolGroups - initToolGroups(extensionManager, ToolGroupService, commandsManager); - - let unsubscribe; - - const activateTool = () => { - ToolBarService.recordInteraction({ - groupId: 'WindowLevel', - itemId: 'WindowLevel', - interactionType: 'tool', - commands: [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'WindowLevel', - }, - context: 'CORNERSTONE', - }, - ], - }); - - // We don't need to reset the active tool whenever a viewport is getting - // added to the toolGroup. - unsubscribe(); - }; + initToolGroups(extensionManager, toolGroupService, commandsManager); - // Since we only have one viewport for the basic cs3d mode and it has - // only one hanging protocol, we can just use the first viewport - ({ unsubscribe } = ToolGroupService.subscribe( - ToolGroupService.EVENTS.VIEWPORT_ADDED, - activateTool - )); - - ToolBarService.init(extensionManager); - ToolBarService.addButtons(toolbarButtons); - ToolBarService.createButtonSection('primary', [ + GoogleSheetsService.init(); + CropDisplayAreaService.init(); + CacheAPIService.init(); + toolbarService.addButtons([...toolbarButtons, ...moreTools]); + toolbarService.createButtonSection('primary', [ 'Zoom', 'WindowLevel', 'Pan', @@ -113,21 +86,29 @@ function modeFactory({ modeConfiguration }) { }, onModeExit: ({ servicesManager }) => { const { - ToolGroupService, + toolGroupService, SyncGroupService, - MeasurementService, - ToolBarService, + measurementService, + toolbarService, GoogleSheetsService, CacheAPIService, - CornerstoneViewportService, + cornerstoneViewportService, + uiDialogService, + uiModalService, } = servicesManager.services; - ToolBarService.reset(); - MeasurementService.clearMeasurements(); - ToolGroupService.destroy(); + + _activatePanelTriggersSubscriptions.forEach(sub => sub.unsubscribe()); + _activatePanelTriggersSubscriptions = []; + + uiDialogService.dismissAll(); + uiModalService.hide(); + toolbarService.reset(); + measurementService.clearMeasurements(); + toolGroupService.destroy(); SyncGroupService.destroy(); GoogleSheetsService.destroy(); CacheAPIService.destroy(); - CornerstoneViewportService.destroy(); + cornerstoneViewportService.destroy(); }, validationTags: { study: [], @@ -137,7 +118,11 @@ function modeFactory({ modeConfiguration }) { const modalities_list = modalities.split('\\'); // Slide Microscopy modality not supported by basic mode yet - return !modalities_list.includes('SM'); + return { + valid: !modalities_list.includes('SM'), + description: + 'The mode does not support studies that ONLY include the following modalities: SM', + }; }, routes: [ { @@ -150,8 +135,8 @@ function modeFactory({ modeConfiguration }) { props: { leftPanels: [gradienthealth.thumbnailList], rightPanels: rightPanels, - leftPanelDefaultClosed: true, - rightPanelDefaultClosed: false, + leftPanelClosed: true, + rightPanelClosed: false, viewports: [ { namespace: gradienthealth.viewport, @@ -188,6 +173,7 @@ function modeFactory({ modeConfiguration }) { dicomsr.sopClassHandler, ], hotkeys: [...hotkeys], + ...modeConfiguration }; } @@ -198,3 +184,4 @@ const mode = { }; export default mode; +export { initToolGroups, moreTools, toolbarButtons }; diff --git a/modes/breast-density-mode/src/initToolGroups.js b/modes/breast-density-mode/src/initToolGroups.js index bd31ea7..3ecab58 100644 --- a/modes/breast-density-mode/src/initToolGroups.js +++ b/modes/breast-density-mode/src/initToolGroups.js @@ -1,7 +1,9 @@ function initDefaultToolGroup( extensionManager, - ToolGroupService, - commandsManager + toolGroupService, + commandsManager, + toolGroupId, + modeLabelConfig ) { const utilityModule = extensionManager.getModuleEntry( '@ohif/extension-cornerstone.utilityModule.tools' @@ -27,7 +29,30 @@ function initDefaultToolGroup( ], passive: [ { toolName: toolNames.Length }, - { toolName: toolNames.ArrowAnnotate }, + { + toolName: toolNames.ArrowAnnotate, + configuration: { + getTextCallback: (callback, eventDetails) => { + if (modeLabelConfig) { + callback(' '); + } else { + commandsManager.runCommand('arrowTextCallback', { + callback, + eventDetails, + }); + } + }, + changeTextCallback: (data, eventDetails, callback) => { + if (modeLabelConfig === undefined) { + commandsManager.runCommand('arrowTextCallback', { + callback, + data, + eventDetails, + }); + } + }, + }, + }, { toolName: toolNames.Bidirectional }, { toolName: toolNames.DragProbe }, { toolName: toolNames.EllipticalROI }, @@ -40,32 +65,18 @@ function initDefaultToolGroup( // disabled }; - const toolsConfig = { - [toolNames.ArrowAnnotate]: { - getTextCallback: (callback, eventDetails) => - commandsManager.runCommand('arrowTextCallback', { - callback, - eventDetails, - }), - - changeTextCallback: (data, eventDetails, callback) => - commandsManager.runCommand('arrowTextCallback', { - callback, - data, - eventDetails, - }), - }, - }; - - const toolGroupId = 'default'; - ToolGroupService.createToolGroupAndAddTools(toolGroupId, tools, toolsConfig); + toolGroupService.createToolGroupAndAddTools(toolGroupId, tools); } -function initSRToolGroup(extensionManager, ToolGroupService, commandsManager) { +function initSRToolGroup(extensionManager, toolGroupService, commandsManager) { const SRUtilityModule = extensionManager.getModuleEntry( '@ohif/extension-cornerstone-dicom-sr.utilityModule.tools' ); + if (!SRUtilityModule) { + return; + } + const CS3DUtilityModule = extensionManager.getModuleEntry( '@ohif/extension-cornerstone.utilityModule.tools' ); @@ -105,7 +116,30 @@ function initSRToolGroup(extensionManager, ToolGroupService, commandsManager) { ], passive: [ { toolName: SRToolNames.SRLength }, - { toolName: SRToolNames.SRArrowAnnotate }, + { + toolName: SRToolNames.SRArrowAnnotate, + configuration: { + getTextCallback: (callback, eventDetails) => { + if (modeLabelConfig) { + callback(' '); + } else { + commandsManager.runCommand('arrowTextCallback', { + callback, + eventDetails, + }); + } + }, + changeTextCallback: (data, eventDetails, callback) => { + if (modeLabelConfig === undefined) { + commandsManager.runCommand('arrowTextCallback', { + callback, + data, + eventDetails, + }); + } + }, + }, + }, { toolName: SRToolNames.SRBidirectional }, { toolName: SRToolNames.SREllipticalROI }, ], @@ -118,30 +152,19 @@ function initSRToolGroup(extensionManager, ToolGroupService, commandsManager) { // disabled }; - const toolsConfig = { - [toolNames.ArrowAnnotate]: { - getTextCallback: (callback, eventDetails) => - commandsManager.runCommand('arrowTextCallback', { - callback, - eventDetails, - }), - - changeTextCallback: (data, eventDetails, callback) => - commandsManager.runCommand('arrowTextCallback', { - callback, - data, - eventDetails, - }), - }, - }; - const toolGroupId = 'SRToolGroup'; - ToolGroupService.createToolGroupAndAddTools(toolGroupId, tools, toolsConfig); + toolGroupService.createToolGroupAndAddTools(toolGroupId, tools); } -function initToolGroups(extensionManager, ToolGroupService, commandsManager) { - initDefaultToolGroup(extensionManager, ToolGroupService, commandsManager); - initSRToolGroup(extensionManager, ToolGroupService, commandsManager); +function initToolGroups(extensionManager, toolGroupService, commandsManager, modeLabelConfig) { + initDefaultToolGroup( + extensionManager, + toolGroupService, + commandsManager, + 'default', + modeLabelConfig + ); + initSRToolGroup(extensionManager, toolGroupService, commandsManager); } export default initToolGroups; diff --git a/modes/breast-density-mode/src/moreTools.ts b/modes/breast-density-mode/src/moreTools.ts new file mode 100644 index 0000000..bdddb7d --- /dev/null +++ b/modes/breast-density-mode/src/moreTools.ts @@ -0,0 +1,190 @@ +import { ToolbarService } from '@ohif/core'; +import { setToolActiveToolbar } from './toolbarButtons'; + +const { createButton } = ToolbarService; + +/* +const ReferenceLinesListeners: RunCommand = [ + { + commandName: 'setSourceViewportForReferenceLinesTool', + context: 'CORNERSTONE', + }, +]; +*/ + +const moreTools = [ + { + id: 'MoreTools', + uiType: 'ohif.splitButton', + props: { + groupId: 'MoreTools', + evaluate: + 'evaluate.group.promoteToPrimaryIfCornerstoneToolNotActiveInTheList', + primary: createButton({ + id: 'Reset', + icon: 'tool-reset', + tooltip: 'Reset View', + label: 'Reset', + commands: 'resetViewport', + evaluate: 'evaluate.action', + }), + secondary: { + icon: 'chevron-down', + label: '', + tooltip: 'More Tools', + }, + items: [ + createButton({ + id: 'Reset', + icon: 'tool-reset', + label: 'Reset View', + tooltip: 'Reset View', + commands: 'resetViewport', + evaluate: 'evaluate.action', + }), + createButton({ + id: 'rotate-right', + icon: 'tool-rotate-right', + label: 'Rotate Right', + tooltip: 'Rotate +90', + commands: 'rotateViewportCW', + evaluate: 'evaluate.action', + }), + createButton({ + id: 'flipHorizontal', + icon: 'tool-flip-horizontal', + label: 'Flip Horizontal', + tooltip: 'Flip Horizontally', + commands: 'flipViewportHorizontal', + evaluate: ['evaluate.viewportProperties.toggle', 'evaluate.not3D'], + }), + /* + createButton({ + id: 'ImageSliceSync', + icon: 'link', + label: 'Image Slice Sync', + tooltip: 'Enable position synchronization on stack viewports', + commands: { + commandName: 'toggleSynchronizer', + commandOptions: { + type: 'imageSlice', + }, + }, + listeners: { + [EVENTS.STACK_VIEWPORT_NEW_STACK]: { + commandName: 'toggleImageSliceSync', + commandOptions: { toggledState: true }, + }, + }, + evaluate: ['evaluate.cornerstone.synchronizer', 'evaluate.not3D'], + }), + createButton({ + id: 'ReferenceLines', + icon: 'tool-referenceLines', + label: 'Reference Lines', + tooltip: 'Show Reference Lines', + commands: 'toggleEnabledDisabledToolbar', + listeners: { + [ViewportGridService.EVENTS.ACTIVE_VIEWPORT_ID_CHANGED]: ReferenceLinesListeners, + [ViewportGridService.EVENTS.VIEWPORTS_READY]: ReferenceLinesListeners, + }, + evaluate: 'evaluate.cornerstoneTool.toggle', + }), + createButton({ + id: 'ImageOverlayViewer', + icon: 'toggle-dicom-overlay', + label: 'Image Overlay', + tooltip: 'Toggle Image Overlay', + commands: 'toggleEnabledDisabledToolbar', + evaluate: 'evaluate.cornerstoneTool.toggle', + }), + */ + createButton({ + id: 'StackScroll', + icon: 'tool-stack-scroll', + label: 'Stack Scroll', + tooltip: 'Stack Scroll', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + createButton({ + id: 'invert', + icon: 'tool-invert', + label: 'Invert', + tooltip: 'Invert Colors', + commands: 'invertViewport', + evaluate: 'evaluate.viewportProperties.toggle', + }), + /* + createButton({ + id: 'Probe', + icon: 'tool-probe', + label: 'Probe', + tooltip: 'Probe', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + createButton({ + id: 'Cine', + icon: 'tool-cine', + label: 'Cine', + tooltip: 'Cine', + commands: 'toggleCine', + evaluate: ['evaluate.cine', 'evaluate.not3D'], + }), + createButton({ + id: 'Angle', + icon: 'tool-angle', + label: 'Angle', + tooltip: 'Angle', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + */ + createButton({ + id: 'Magnify', + icon: 'tool-magnify', + label: 'Zoom-in', + tooltip: 'Zoom-in', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + /* + createButton({ + id: 'CalibrationLine', + icon: 'tool-calibration', + label: 'Calibration', + tooltip: 'Calibration Line', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + createButton({ + id: 'TagBrowser', + icon: 'dicom-tag-browser', + label: 'Dicom Tag Browser', + tooltip: 'Dicom Tag Browser', + commands: 'openDICOMTagViewer', + }), + createButton({ + id: 'AdvancedMagnify', + icon: 'icon-tool-loupe', + label: 'Magnify Probe', + tooltip: 'Magnify Probe', + commands: 'toggleActiveDisabledToolbar', + evaluate: 'evaluate.cornerstoneTool.toggle.ifStrictlyDisabled', + }), + createButton({ + id: 'UltrasoundDirectionalTool', + icon: 'icon-tool-ultrasound-bidirectional', + label: 'Ultrasound Directional', + tooltip: 'Ultrasound Directional', + commands: setToolActiveToolbar, + evaluate: ['evaluate.cornerstoneTool', 'evaluate.isUS'], + }), + */ + ], + }, + }, +]; + +export default moreTools; diff --git a/modes/breast-density-mode/src/toolbarButtons.js b/modes/breast-density-mode/src/toolbarButtons.js index c4231d8..6f8ad84 100644 --- a/modes/breast-density-mode/src/toolbarButtons.js +++ b/modes/breast-density-mode/src/toolbarButtons.js @@ -1,404 +1,198 @@ // TODO: torn, can either bake this here; or have to create a whole new button type // Only ways that you can pass in a custom React component for render :l -import { - // ExpandableToolbarButton, - // ListMenu, - WindowLevelMenuItem, -} from '@ohif/ui'; -import windowLevelPresets from './windowLevelPresets'; +import { ToolbarService } from '@ohif/core'; -/** - * - * @param {*} type - 'tool' | 'action' | 'toggle' - * @param {*} id - * @param {*} icon - * @param {*} label - */ -function _createButton(type, id, icon, label, commands, tooltip) { - return { - id, - icon, - label, - type, - commands, - tooltip, - }; -} +const { createButton } = ToolbarService; -const _createActionButton = _createButton.bind(null, 'action'); -const _createToggleButton = _createButton.bind(null, 'toggle'); -const _createToolButton = _createButton.bind(null, 'tool'); - -/** - * - * @param {*} preset - preset number (from above import) - * @param {*} title - * @param {*} subtitle - */ -function _createWwwcPreset(preset, title, subtitle) { - return { - id: preset.toString(), - title, - subtitle, - type: 'action', - commands: [ - { - commandName: 'setWindowLevel', - commandOptions: { - ...windowLevelPresets[preset], - }, - context: 'CORNERSTONE', - }, - ], - }; -} +export const setToolActiveToolbar = { + commandName: 'setToolActiveToolbar', + commandOptions: { + toolGroupIds: ['default', 'mpr', 'SRToolGroup', 'volume3d'], + }, +}; const toolbarButtons = [ // Measurement + /* { id: 'MeasurementTools', - type: 'ohif.splitButton', + uiType: 'ohif.splitButton', props: { groupId: 'MeasurementTools', - isRadio: true, // ? - // Switch? - primary: _createToolButton( - 'Length', - 'tool-length', - 'Length', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'Length', - }, - context: 'CORNERSTONE', - }, - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'SRLength', - toolGroupId: 'SRToolGroup', - }, - // we can use the setToolActive command for this from Cornerstone commandsModule - context: 'CORNERSTONE', - }, - ], - 'Length' - ), + // group evaluate to determine which item should move to the top + evaluate: + 'evaluate.group.promoteToPrimaryIfCornerstoneToolNotActiveInTheList', + primary: createButton({ + id: 'Length', + icon: 'tool-length', + label: 'Length', + tooltip: 'Length Tool', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), secondary: { icon: 'chevron-down', - label: '', - isActive: true, tooltip: 'More Measure Tools', }, items: [ - _createToolButton( - 'Length', - 'tool-length', - 'Length', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'Length', - }, - context: 'CORNERSTONE', - }, - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'SRLength', - toolGroupId: 'SRToolGroup', - }, - // we can use the setToolActive command for this from Cornerstone commandsModule - context: 'CORNERSTONE', - }, - ], - 'Length Tool' - ), - _createToolButton( - 'Bidirectional', - 'tool-bidirectional', - 'Bidirectional', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'Bidirectional', - }, - context: 'CORNERSTONE', - }, - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'SRBidirectional', - toolGroupId: 'SRToolGroup', - }, - context: 'CORNERSTONE', - }, - ], - 'Bidirectional Tool' - ), - _createToolButton( - 'ArrowAnnotate', - 'tool-annotate', - 'Annotation', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'ArrowAnnotate', - }, - context: 'CORNERSTONE', - }, - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'SRArrowAnnotate', - toolGroupId: 'SRToolGroup', - }, - context: 'CORNERSTONE', - }, - ], - 'Arrow Annotate' - ), - _createToolButton( - 'EllipticalROI', - 'tool-elipse', - 'Ellipse', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'EllipticalROI', - }, - context: 'CORNERSTONE', - }, - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'SREllipticalROI', - toolGroupId: 'SRToolGroup', - }, - context: 'CORNERSTONE', - }, - ], - 'Ellipse Tool' - ), + createButton({ + id: 'Length', + icon: 'tool-length', + label: 'Length', + tooltip: 'Length Tool', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + createButton({ + id: 'Bidirectional', + icon: 'tool-bidirectional', + label: 'Bidirectional', + tooltip: 'Bidirectional Tool', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + createButton({ + id: 'ArrowAnnotate', + icon: 'tool-annotate', + label: 'Annotation', + tooltip: 'Arrow Annotate', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + createButton({ + id: 'EllipticalROI', + icon: 'tool-ellipse', + label: 'Ellipse', + tooltip: 'Ellipse ROI', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + createButton({ + id: 'CircleROI', + icon: 'tool-circle', + label: 'Circle', + tooltip: 'Circle Tool', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + createButton({ + id: 'PlanarFreehandROI', + icon: 'icon-tool-freehand-roi', + label: 'Freehand ROI', + tooltip: 'Freehand ROI', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + createButton({ + id: 'SplineROI', + icon: 'icon-tool-spline-roi', + label: 'Spline ROI', + tooltip: 'Spline ROI', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + createButton({ + id: 'LivewireContour', + icon: 'icon-tool-livewire', + label: 'Livewire tool', + tooltip: 'Livewire tool', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), ], }, }, + */ // Zoom.. { id: 'Zoom', - type: 'ohif.radioGroup', + uiType: 'ohif.radioGroup', props: { - type: 'tool', icon: 'tool-zoom', label: 'Zoom', - commands: [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'Zoom', - }, - context: 'CORNERSTONE', - }, - ], + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', }, }, - // Window Level + Presets... + // Window Level { id: 'WindowLevel', - type: 'ohif.splitButton', + uiType: 'ohif.radioGroup', props: { - groupId: 'WindowLevel', - primary: _createToolButton( - 'WindowLevel', - 'tool-window-level', - 'Window Level', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'WindowLevel', - }, - context: 'CORNERSTONE', - }, - ], - 'Window Level' - ), - secondary: { - icon: 'chevron-down', - label: 'W/L Manual', - isActive: true, - tooltip: 'W/L Presets', - }, - isAction: true, // ? - renderer: WindowLevelMenuItem, - items: [ - _createWwwcPreset(1, 'Normal', '2850 / 900'), - _createWwwcPreset(2, 'Harder', '2850 / 750'), - _createWwwcPreset(3, 'Softer', '2850 / 1050'), - ], + icon: 'tool-window-level', + label: 'Window Level', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', }, }, // Pan... { id: 'Pan', - type: 'ohif.radioGroup', + uiType: 'ohif.radioGroup', props: { type: 'tool', icon: 'tool-move', label: 'Pan', - commands: [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'Pan', - }, - context: 'CORNERSTONE', - }, - ], + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }, + }, + /* + { + id: 'TrackballRotate', + uiType: 'ohif.radioGroup', + props: { + type: 'tool', + icon: 'tool-3d-rotate', + label: '3D Rotate', + commands: setToolActiveToolbar, + evaluate: { + name: 'evaluate.cornerstoneTool', + disabledText: 'Select a 3D viewport to enable this tool', + }, }, }, + */ { id: 'Capture', - type: 'ohif.action', + uiType: 'ohif.radioGroup', props: { icon: 'tool-capture', label: 'Capture', - type: 'action', - commands: [ - { - commandName: 'showDownloadViewportModal', - commandOptions: {}, - context: 'CORNERSTONE', - }, - ], + commands: 'showDownloadViewportModal', + evaluate: 'evaluate.action', }, }, { id: 'Layout', - type: 'ohif.layoutSelector', + uiType: 'ohif.layoutSelector', props: { rows: 2, - columns: 4 - } + columns: 4, + evaluate: 'evaluate.action', + }, }, - // More... + /* { - id: 'MoreTools', - type: 'ohif.splitButton', + id: 'Crosshairs', + uiType: 'ohif.radioGroup', props: { - isRadio: true, // ? - groupId: 'MoreTools', - primary: _createActionButton( - 'Reset', - 'tool-reset', - 'Reset View', - [ - { - commandName: 'resetViewport', - commandOptions: {}, - context: 'CORNERSTONE', - }, - ], - 'Reset' - ), - secondary: { - icon: 'chevron-down', - label: '', - isActive: true, - tooltip: 'More Tools', + type: 'tool', + icon: 'tool-crosshair', + label: 'Crosshairs', + commands: { + commandName: 'setToolActiveToolbar', + commandOptions: { + toolGroupIds: ['mpr'], + }, + }, + evaluate: { + name: 'evaluate.cornerstoneTool', + disabledText: 'Select an MPR viewport to enable this tool', }, - items: [ - _createActionButton( - 'Reset', - 'tool-reset', - 'Reset View', - [ - { - commandName: 'resetViewport', - commandOptions: {}, - context: 'CORNERSTONE', - }, - ], - 'Reset' - ), - _createActionButton( - 'rotate-right', - 'tool-rotate-right', - 'Rotate Right', - [ - { - commandName: 'rotateViewportCW', - commandOptions: {}, - context: 'CORNERSTONE', - }, - ], - 'Rotate +90' - ), - _createActionButton( - 'flip-horizontal', - 'tool-flip-horizontal', - 'Flip Horizontally', - [ - { - commandName: 'flipViewportHorizontal', - commandOptions: {}, - context: 'CORNERSTONE', - }, - ], - 'Flip Horizontal' - ), - _createToolButton( - 'StackScroll', - 'tool-stack-scroll', - 'Stack Scroll', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'StackScroll', - }, - context: 'CORNERSTONE', - }, - ], - 'Stack Scroll' - ), - _createActionButton( - 'invert', - 'tool-invert', - 'Invert', - [ - { - commandName: 'invertViewport', - commandOptions: {}, - context: 'CORNERSTONE', - }, - ], - 'Invert Colors' - ), - _createToolButton( - 'Magnify', - 'tool-magnify', - 'Magnify', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'Magnify', - }, - context: 'CORNERSTONE', - }, - ], - 'Magnify' - ), - ], }, }, + */ ]; export default toolbarButtons; diff --git a/modes/cohort/src/index.tsx b/modes/cohort/src/index.tsx index a76199b..c710026 100644 --- a/modes/cohort/src/index.tsx +++ b/modes/cohort/src/index.tsx @@ -1,5 +1,6 @@ import { hotkeys } from '@ohif/core'; import toolbarButtons from './toolbarButtons.js'; +import moreTools from './moreTools.ts'; import { id } from './id.js'; import initToolGroups from './initToolGroups.js'; @@ -56,7 +57,7 @@ const extensionDependencies = { '@ohif/extension-dicom-video': '^3.0.1', }; -function modeFactory() { +function modeFactory({ modeConfiguration }) { let _activatePanelTriggersSubscriptions = []; return { // TODO: We're using this as a route segment @@ -72,47 +73,14 @@ function modeFactory() { measurementService, toolbarService, toolGroupService, - panelService, - segmentationService, } = servicesManager.services; measurementService.clearMeasurements(); // Init Default and SR ToolGroups - initToolGroups(extensionManager, toolGroupService, commandsManager); - - let unsubscribe; - - const activateTool = () => { - toolbarService.recordInteraction({ - groupId: 'WindowLevel', - itemId: 'WindowLevel', - interactionType: 'tool', - commands: [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'WindowLevel', - }, - context: 'CORNERSTONE', - }, - ], - }); - - // We don't need to reset the active tool whenever a viewport is getting - // added to the toolGroup. - unsubscribe(); - }; - - // Since we only have one viewport for the basic cs3d mode and it has - // only one hanging protocol, we can just use the first viewport - ({ unsubscribe } = toolGroupService.subscribe( - toolGroupService.EVENTS.VIEWPORT_ADDED, - activateTool - )); + initToolGroups(extensionManager, toolGroupService, commandsManager, this.labelConfig); - toolbarService.init(extensionManager); - toolbarService.addButtons(toolbarButtons); + toolbarService.addButtons([...toolbarButtons, ...moreTools]); toolbarService.createButtonSection('primary', [ 'MeasurementTools', 'Zoom', @@ -132,12 +100,17 @@ function modeFactory() { toolbarService, segmentationService, cornerstoneViewportService, + uiDialogService, + uiModalService, } = servicesManager.services; _activatePanelTriggersSubscriptions.forEach(sub => sub.unsubscribe()); _activatePanelTriggersSubscriptions = []; + uiDialogService.dismissAll(); + uiModalService.hide(); toolGroupService.destroy(); + toolbarService.reset(); syncGroupService.destroy(); segmentationService.destroy(); cornerstoneViewportService.destroy(); @@ -151,9 +124,13 @@ function modeFactory() { const modalities_list = modalities.split('\\'); // Exclude non-image modalities - return !!modalities_list.filter( - modality => NON_IMAGE_MODALITIES.indexOf(modality) === -1 - ).length; + return { + valid: !!modalities_list.filter( + (modality) => NON_IMAGE_MODALITIES.indexOf(modality) === -1 + ).length, + description: + 'The mode does not support studies that ONLY include the following modalities: SM, ECG, SR, SEG, RTSTRUCT', + }; }, routes: [ { @@ -167,7 +144,7 @@ function modeFactory() { props: { leftPanels: [tracked.thumbnailList], rightPanels: [], - rightPanelDefaultClosed: true, + rightPanelClosed: true, viewports: [ { namespace: tracked.viewport, @@ -210,6 +187,7 @@ function modeFactory() { dicomsr.sopClassHandler, ], hotkeys: [...hotkeys.defaults.hotkeyBindings], + ...modeConfiguration }; } @@ -220,3 +198,4 @@ const mode = { }; export default mode; +export { initToolGroups, moreTools, toolbarButtons }; diff --git a/modes/cohort/src/initToolGroups.js b/modes/cohort/src/initToolGroups.js index 6bbe668..562abdf 100644 --- a/modes/cohort/src/initToolGroups.js +++ b/modes/cohort/src/initToolGroups.js @@ -28,42 +28,58 @@ function initDefaultToolGroup( ], passive: [ { toolName: toolNames.Length }, - { toolName: toolNames.ArrowAnnotate }, + { + toolName: toolNames.ArrowAnnotate, + configuration: { + getTextCallback: (callback, eventDetails) => { + if (modeLabelConfig) { + callback(' '); + } else { + commandsManager.runCommand('arrowTextCallback', { + callback, + eventDetails, + }); + } + }, + changeTextCallback: (data, eventDetails, callback) => { + if (modeLabelConfig === undefined) { + commandsManager.runCommand('arrowTextCallback', { + callback, + data, + eventDetails, + }); + } + }, + }, + }, { toolName: toolNames.Bidirectional }, { toolName: toolNames.DragProbe }, + { toolName: toolNames.Probe }, { toolName: toolNames.EllipticalROI }, + { toolName: toolNames.CircleROI }, { toolName: toolNames.RectangleROI }, { toolName: toolNames.StackScroll }, { toolName: toolNames.Angle }, { toolName: toolNames.CobbAngle }, - { toolName: toolNames.PlanarFreehandROI }, { toolName: toolNames.Magnify }, { toolName: toolNames.SegmentationDisplay }, { toolName: toolNames.CalibrationLine }, + + { toolName: toolNames.UltrasoundDirectional }, + { toolName: toolNames.PlanarFreehandROI }, + { toolName: toolNames.SplineROI }, + { toolName: toolNames.LivewireContour }, + ], + enabled: [{ toolName: toolNames.ImageOverlayViewer }, { toolName: toolNames.ReferenceLines },], + disabled: [ + { + toolName: toolNames.AdvancedMagnify, + }, ], - // enabled - // disabled - disabled: [{ toolName: toolNames.ReferenceLines }], }; - const toolsConfig = { - [toolNames.ArrowAnnotate]: { - getTextCallback: (callback, eventDetails) => - commandsManager.runCommand('arrowTextCallback', { - callback, - eventDetails, - }), - - changeTextCallback: (data, eventDetails, callback) => - commandsManager.runCommand('arrowTextCallback', { - callback, - data, - eventDetails, - }), - }, - }; - toolGroupService.createToolGroupAndAddTools(toolGroupId, tools, toolsConfig); + toolGroupService.createToolGroupAndAddTools(toolGroupId, tools); } function initSRToolGroup(extensionManager, toolGroupService, commandsManager) { @@ -71,6 +87,10 @@ function initSRToolGroup(extensionManager, toolGroupService, commandsManager) { '@ohif/extension-cornerstone-dicom-sr.utilityModule.tools' ); + if (!SRUtilityModule) { + return; + } + const CS3DUtilityModule = extensionManager.getModuleEntry( '@ohif/extension-cornerstone.utilityModule.tools' ); @@ -113,6 +133,9 @@ function initSRToolGroup(extensionManager, toolGroupService, commandsManager) { { toolName: SRToolNames.SRArrowAnnotate }, { toolName: SRToolNames.SRBidirectional }, { toolName: SRToolNames.SREllipticalROI }, + { toolName: SRToolNames.SRCircleROI }, + { toolName: SRToolNames.SRPlanarFreehandROI }, + { toolName: SRToolNames.SRRectangleROI }, ], enabled: [ { @@ -123,25 +146,8 @@ function initSRToolGroup(extensionManager, toolGroupService, commandsManager) { // disabled }; - const toolsConfig = { - [toolNames.ArrowAnnotate]: { - getTextCallback: (callback, eventDetails) => - commandsManager.runCommand('arrowTextCallback', { - callback, - eventDetails, - }), - - changeTextCallback: (data, eventDetails, callback) => - commandsManager.runCommand('arrowTextCallback', { - callback, - data, - eventDetails, - }), - }, - }; - const toolGroupId = 'SRToolGroup'; - toolGroupService.createToolGroupAndAddTools(toolGroupId, tools, toolsConfig); + toolGroupService.createToolGroupAndAddTools(toolGroupId, tools); } function initMPRToolGroup(extensionManager, toolGroupService, commandsManager) { @@ -169,10 +175,35 @@ function initMPRToolGroup(extensionManager, toolGroupService, commandsManager) { ], passive: [ { toolName: toolNames.Length }, - { toolName: toolNames.ArrowAnnotate }, + { + toolName: toolNames.ArrowAnnotate, + configuration: { + getTextCallback: (callback, eventDetails) => { + if (modeLabelConfig) { + callback(''); + } else { + commandsManager.runCommand('arrowTextCallback', { + callback, + eventDetails, + }); + } + }, + changeTextCallback: (data, eventDetails, callback) => { + if (modeLabelConfig === undefined) { + commandsManager.runCommand('arrowTextCallback', { + callback, + data, + eventDetails, + }); + } + }, + }, + }, { toolName: toolNames.Bidirectional }, { toolName: toolNames.DragProbe }, + { toolName: toolNames.Probe }, { toolName: toolNames.EllipticalROI }, + { toolName: toolNames.CircleROI }, { toolName: toolNames.RectangleROI }, { toolName: toolNames.StackScroll }, { toolName: toolNames.Angle }, @@ -181,39 +212,25 @@ function initMPRToolGroup(extensionManager, toolGroupService, commandsManager) { { toolName: toolNames.SegmentationDisplay }, ], disabled: [ - { toolName: toolNames.Crosshairs }, + { + toolName: toolNames.Crosshairs, + configuration: { + viewportIndicators: false, + disableOnPassive: true, + autoPan: { + enabled: false, + panSize: 10, + }, + }, + }, + { + toolName: toolNames.AdvancedMagnify, + }, { toolName: toolNames.ReferenceLines }, ], - - // enabled - // disabled - }; - - const toolsConfig = { - [toolNames.Crosshairs]: { - viewportIndicators: false, - autoPan: { - enabled: false, - panSize: 10, - }, - }, - [toolNames.ArrowAnnotate]: { - getTextCallback: (callback, eventDetails) => - commandsManager.runCommand('arrowTextCallback', { - callback, - eventDetails, - }), - - changeTextCallback: (data, eventDetails, callback) => - commandsManager.runCommand('arrowTextCallback', { - callback, - data, - eventDetails, - }), - }, }; - toolGroupService.createToolGroupAndAddTools('mpr', tools, toolsConfig); + toolGroupService.createToolGroupAndAddTools('mpr', tools); } function initVolume3DToolGroup(extensionManager, toolGroupService) { const utilityModule = extensionManager.getModuleEntry( @@ -242,15 +259,16 @@ function initVolume3DToolGroup(extensionManager, toolGroupService) { toolGroupService.createToolGroupAndAddTools('volume3d', tools); } -function initToolGroups(extensionManager, toolGroupService, commandsManager) { +function initToolGroups(extensionManager, toolGroupService, commandsManager, modeLabelConfig) { initDefaultToolGroup( extensionManager, toolGroupService, commandsManager, - 'default' + 'default', + modeLabelConfig ); initSRToolGroup(extensionManager, toolGroupService, commandsManager); - initMPRToolGroup(extensionManager, toolGroupService, commandsManager); + initMPRToolGroup(extensionManager, toolGroupService, commandsManager, modeLabelConfig); initVolume3DToolGroup(extensionManager, toolGroupService); } diff --git a/modes/cohort/src/moreTools.ts b/modes/cohort/src/moreTools.ts new file mode 100644 index 0000000..54b3c10 --- /dev/null +++ b/modes/cohort/src/moreTools.ts @@ -0,0 +1,189 @@ +import type { RunCommand } from '@ohif/core/types'; +import { EVENTS } from '@cornerstonejs/core'; +import { ToolbarService, ViewportGridService } from '@ohif/core'; +import { setToolActiveToolbar } from './toolbarButtons'; +const { createButton } = ToolbarService; + +const ReferenceLinesListeners: RunCommand = [ + { + commandName: 'setSourceViewportForReferenceLinesTool', + context: 'CORNERSTONE', + }, +]; + +const moreTools = [ + { + id: 'MoreTools', + uiType: 'ohif.splitButton', + props: { + groupId: 'MoreTools', + evaluate: + 'evaluate.group.promoteToPrimaryIfCornerstoneToolNotActiveInTheList', + primary: createButton({ + id: 'Reset', + icon: 'tool-reset', + tooltip: 'Reset View', + label: 'Reset', + commands: 'resetViewport', + evaluate: 'evaluate.action', + }), + secondary: { + icon: 'chevron-down', + label: '', + tooltip: 'More Tools', + }, + items: [ + createButton({ + id: 'Reset', + icon: 'tool-reset', + label: 'Reset View', + tooltip: 'Reset View', + commands: 'resetViewport', + evaluate: 'evaluate.action', + }), + createButton({ + id: 'rotate-right', + icon: 'tool-rotate-right', + label: 'Rotate Right', + tooltip: 'Rotate +90', + commands: 'rotateViewportCW', + evaluate: 'evaluate.action', + }), + createButton({ + id: 'flipHorizontal', + icon: 'tool-flip-horizontal', + label: 'Flip Horizontal', + tooltip: 'Flip Horizontally', + commands: 'flipViewportHorizontal', + evaluate: ['evaluate.viewportProperties.toggle', 'evaluate.not3D'], + }), + createButton({ + id: 'ImageSliceSync', + icon: 'link', + label: 'Image Slice Sync', + tooltip: 'Enable position synchronization on stack viewports', + commands: { + commandName: 'toggleSynchronizer', + commandOptions: { + type: 'imageSlice', + }, + }, + listeners: { + [EVENTS.STACK_VIEWPORT_NEW_STACK]: { + commandName: 'toggleImageSliceSync', + commandOptions: { toggledState: true }, + }, + }, + evaluate: ['evaluate.cornerstone.synchronizer', 'evaluate.not3D'], + }), + createButton({ + id: 'ReferenceLines', + icon: 'tool-referenceLines', + label: 'Reference Lines', + tooltip: 'Show Reference Lines', + commands: 'toggleEnabledDisabledToolbar', + listeners: { + [ViewportGridService.EVENTS.ACTIVE_VIEWPORT_ID_CHANGED]: + ReferenceLinesListeners, + [ViewportGridService.EVENTS.VIEWPORTS_READY]: + ReferenceLinesListeners, + }, + evaluate: 'evaluate.cornerstoneTool.toggle', + }), + /* + createButton({ + id: 'ImageOverlayViewer', + icon: 'toggle-dicom-overlay', + label: 'Image Overlay', + tooltip: 'Toggle Image Overlay', + commands: 'toggleEnabledDisabledToolbar', + evaluate: 'evaluate.cornerstoneTool.toggle', + }), + */ + createButton({ + id: 'StackScroll', + icon: 'tool-stack-scroll', + label: 'Stack Scroll', + tooltip: 'Stack Scroll', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + createButton({ + id: 'invert', + icon: 'tool-invert', + label: 'Invert', + tooltip: 'Invert Colors', + commands: 'invertViewport', + evaluate: 'evaluate.viewportProperties.toggle', + }), + createButton({ + id: 'Probe', + icon: 'tool-probe', + label: 'Probe', + tooltip: 'Probe', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + createButton({ + id: 'Cine', + icon: 'tool-cine', + label: 'Cine', + tooltip: 'Cine', + commands: 'toggleCine', + evaluate: ['evaluate.cine', 'evaluate.not3D'], + }), + createButton({ + id: 'Angle', + icon: 'tool-angle', + label: 'Angle', + tooltip: 'Angle', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + createButton({ + id: 'Magnify', + icon: 'tool-magnify', + label: 'Zoom-in', + tooltip: 'Zoom-in', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + createButton({ + id: 'CalibrationLine', + icon: 'tool-calibration', + label: 'Calibration', + tooltip: 'Calibration Line', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + createButton({ + id: 'TagBrowser', + icon: 'dicom-tag-browser', + label: 'Dicom Tag Browser', + tooltip: 'Dicom Tag Browser', + commands: 'openDICOMTagViewer', + }), + /* + createButton({ + id: 'AdvancedMagnify', + icon: 'icon-tool-loupe', + label: 'Magnify Probe', + tooltip: 'Magnify Probe', + commands: 'toggleActiveDisabledToolbar', + evaluate: 'evaluate.cornerstoneTool.toggle.ifStrictlyDisabled', + }), + createButton({ + id: 'UltrasoundDirectionalTool', + icon: 'icon-tool-ultrasound-bidirectional', + label: 'Ultrasound Directional', + tooltip: 'Ultrasound Directional', + commands: setToolActiveToolbar, + evaluate: ['evaluate.cornerstoneTool', 'evaluate.isUS'], + }), + */ + ], + }, + }, +]; + +export default moreTools; diff --git a/modes/cohort/src/toolbarButtons.js b/modes/cohort/src/toolbarButtons.js index a2b72cb..2526890 100644 --- a/modes/cohort/src/toolbarButtons.js +++ b/modes/cohort/src/toolbarButtons.js @@ -1,305 +1,189 @@ // TODO: torn, can either bake this here; or have to create a whole new button type // Only ways that you can pass in a custom React component for render :l -import { - // ExpandableToolbarButton, - // ListMenu, - WindowLevelMenuItem, -} from '@ohif/ui'; -import { defaults } from '@ohif/core'; +import { ToolbarService } from '@ohif/core'; -const { windowLevelPresets } = defaults; -/** - * - * @param {*} type - 'tool' | 'action' | 'toggle' - * @param {*} id - * @param {*} icon - * @param {*} label - */ -function _createButton(type, id, icon, label, commands, tooltip, uiType) { - return { - id, - icon, - label, - type, - commands, - tooltip, - uiType, - }; -} +const { createButton } = ToolbarService; -const _createActionButton = _createButton.bind(null, 'action'); -const _createToggleButton = _createButton.bind(null, 'toggle'); -const _createToolButton = _createButton.bind(null, 'tool'); - -/** - * - * @param {*} preset - preset number (from above import) - * @param {*} title - * @param {*} subtitle - */ -function _createWwwcPreset(preset, title, subtitle) { - return { - id: preset.toString(), - title, - subtitle, - type: 'action', - commands: [ - { - commandName: 'setWindowLevel', - commandOptions: { - ...windowLevelPresets[preset], - }, - context: 'CORNERSTONE', - }, - ], - }; -} - -const toolGroupIds = ['default', 'mpr', 'SRToolGroup']; - -/** - * Creates an array of 'setToolActive' commands for the given toolName - one for - * each toolGroupId specified in toolGroupIds. - * @param {string} toolName - * @returns {Array} an array of 'setToolActive' commands - */ -function _createSetToolActiveCommands(toolName) { - const temp = toolGroupIds.map(toolGroupId => ({ - commandName: 'setToolActive', - commandOptions: { - toolGroupId, - toolName, - }, - context: 'CORNERSTONE', - })); - return temp; -} +export const setToolActiveToolbar = { + commandName: 'setToolActiveToolbar', + commandOptions: { + toolGroupIds: ['default', 'mpr', 'SRToolGroup', 'volume3d'], + }, +}; const toolbarButtons = [ // Measurement { id: 'MeasurementTools', - type: 'ohif.splitButton', + uiType: 'ohif.splitButton', props: { groupId: 'MeasurementTools', - isRadio: true, // ? - // Switch? - primary: _createToolButton( - 'Length', - 'tool-length', - 'Length', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'Length', - }, - context: 'CORNERSTONE', - }, - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'SRLength', - toolGroupId: 'SRToolGroup', - }, - // we can use the setToolActive command for this from Cornerstone commandsModule - context: 'CORNERSTONE', - }, - ], - 'Length' - ), + // group evaluate to determine which item should move to the top + evaluate: + 'evaluate.group.promoteToPrimaryIfCornerstoneToolNotActiveInTheList', + primary: createButton({ + id: 'Length', + icon: 'tool-length', + label: 'Length', + tooltip: 'Length Tool', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), secondary: { icon: 'chevron-down', - label: '', - isActive: true, tooltip: 'More Measure Tools', }, items: [ - _createToolButton( - 'Length', - 'tool-length', - 'Length', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'Length', - }, - context: 'CORNERSTONE', - }, - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'SRLength', - toolGroupId: 'SRToolGroup', - }, - // we can use the setToolActive command for this from Cornerstone commandsModule - context: 'CORNERSTONE', - }, - ], - 'Length Tool' - ), - _createToolButton( - 'Bidirectional', - 'tool-bidirectional', - 'Bidirectional', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'Bidirectional', - }, - context: 'CORNERSTONE', - }, - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'SRBidirectional', - toolGroupId: 'SRToolGroup', - }, - context: 'CORNERSTONE', - }, - ], - 'Bidirectional Tool' - ), - _createToolButton( - 'ArrowAnnotate', - 'tool-annotate', - 'Annotation', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'ArrowAnnotate', - }, - context: 'CORNERSTONE', - }, - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'SRArrowAnnotate', - toolGroupId: 'SRToolGroup', - }, - context: 'CORNERSTONE', - }, - ], - 'Arrow Annotate' - ), - _createToolButton( - 'EllipticalROI', - 'tool-elipse', - 'Ellipse', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'EllipticalROI', - }, - context: 'CORNERSTONE', - }, - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'SREllipticalROI', - toolGroupId: 'SRToolGroup', - }, - context: 'CORNERSTONE', - }, - ], - 'Ellipse Tool' - ), + createButton({ + id: 'Length', + icon: 'tool-length', + label: 'Length', + tooltip: 'Length Tool', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + createButton({ + id: 'Bidirectional', + icon: 'tool-bidirectional', + label: 'Bidirectional', + tooltip: 'Bidirectional Tool', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + createButton({ + id: 'ArrowAnnotate', + icon: 'tool-annotate', + label: 'Annotation', + tooltip: 'Arrow Annotate', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + createButton({ + id: 'EllipticalROI', + icon: 'tool-ellipse', + label: 'Ellipse', + tooltip: 'Ellipse ROI', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + createButton({ + id: 'RectangleROI', + icon: 'tool-rectangle', + label: 'Rectangle', + tooltip: 'Rectangle ROI', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + /* + createButton({ + id: 'CircleROI', + icon: 'tool-circle', + label: 'Circle', + tooltip: 'Circle Tool', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + createButton({ + id: 'PlanarFreehandROI', + icon: 'icon-tool-freehand-roi', + label: 'Freehand ROI', + tooltip: 'Freehand ROI', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + createButton({ + id: 'SplineROI', + icon: 'icon-tool-spline-roi', + label: 'Spline ROI', + tooltip: 'Spline ROI', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + createButton({ + id: 'LivewireContour', + icon: 'icon-tool-livewire', + label: 'Livewire tool', + tooltip: 'Livewire tool', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + */ ], }, }, // Zoom.. { id: 'Zoom', - type: 'ohif.radioGroup', + uiType: 'ohif.radioGroup', props: { - type: 'tool', icon: 'tool-zoom', label: 'Zoom', - commands: _createSetToolActiveCommands('Zoom'), + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', }, }, // Window Level + Presets... { id: 'WindowLevel', - type: 'ohif.splitButton', + uiType: 'ohif.radioGroup', props: { - groupId: 'WindowLevel', - primary: _createToolButton( - 'WindowLevel', - 'tool-window-level', - 'Window Level', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'WindowLevel', - }, - context: 'CORNERSTONE', - }, - ], - 'Window Level' - ), - secondary: { - icon: 'chevron-down', - label: 'W/L Manual', - isActive: true, - tooltip: 'W/L Presets', - }, - isAction: true, // ? - renderer: WindowLevelMenuItem, - items: [ - _createWwwcPreset(1, 'Soft tissue', '400 / 40'), - _createWwwcPreset(2, 'Lung', '1500 / -600'), - _createWwwcPreset(3, 'Liver', '150 / 90'), - _createWwwcPreset(4, 'Bone', '2500 / 480'), - _createWwwcPreset(5, 'Brain', '80 / 40'), - ], + icon: 'tool-window-level', + label: 'Window Level', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', }, }, // Pan... { id: 'Pan', - type: 'ohif.radioGroup', + uiType: 'ohif.radioGroup', props: { type: 'tool', icon: 'tool-move', label: 'Pan', - commands: _createSetToolActiveCommands('Pan'), + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }, + }, + /* + { + id: 'TrackballRotate', + uiType: 'ohif.radioGroup', + props: { + type: 'tool', + icon: 'tool-3d-rotate', + label: '3D Rotate', + commands: setToolActiveToolbar, + evaluate: { + name: 'evaluate.cornerstoneTool', + disabledText: 'Select a 3D viewport to enable this tool', + }, }, }, + */ { id: 'Capture', - type: 'ohif.action', + uiType: 'ohif.radioGroup', props: { icon: 'tool-capture', label: 'Capture', - type: 'action', - commands: [ - { - commandName: 'showDownloadViewportModal', - commandOptions: {}, - context: 'CORNERSTONE', - }, - ], + commands: 'showDownloadViewportModal', + evaluate: 'evaluate.action', }, }, { id: 'Layout', - type: 'ohif.layoutSelector', + uiType: 'ohif.layoutSelector', props: { rows: 3, - columns: 3, + columns: 4, + evaluate: 'evaluate.action', }, }, { id: 'MPR', - type: 'ohif.action', + uiType: 'ohif.radioGroup', props: { - type: 'toggle', icon: 'icon-mpr', label: 'MPR', commands: [ @@ -308,276 +192,28 @@ const toolbarButtons = [ commandOptions: { protocolId: 'mpr', }, - context: 'DEFAULT', }, ], + evaluate: 'evaluate.mpr', }, }, { id: 'Crosshairs', - type: 'ohif.radioGroup', + uiType: 'ohif.radioGroup', props: { type: 'tool', icon: 'tool-crosshair', label: 'Crosshairs', - commands: [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'Crosshairs', - toolGroupId: 'mpr', - }, - context: 'CORNERSTONE', + commands: { + commandName: 'setToolActiveToolbar', + commandOptions: { + toolGroupIds: ['mpr'], }, - ], - }, - }, - // More... - { - id: 'MoreTools', - type: 'ohif.splitButton', - props: { - isRadio: true, // ? - groupId: 'MoreTools', - primary: _createActionButton( - 'Reset', - 'tool-reset', - 'Reset View', - [ - { - commandName: 'resetViewport', - commandOptions: {}, - context: 'CORNERSTONE', - }, - ], - 'Reset' - ), - secondary: { - icon: 'chevron-down', - label: '', - isActive: true, - tooltip: 'More Tools', }, - items: [ - _createActionButton( - 'Reset', - 'tool-reset', - 'Reset View', - [ - { - commandName: 'resetViewport', - commandOptions: {}, - context: 'CORNERSTONE', - }, - ], - 'Reset' - ), - _createActionButton( - 'rotate-right', - 'tool-rotate-right', - 'Rotate Right', - [ - { - commandName: 'rotateViewportCW', - commandOptions: {}, - context: 'CORNERSTONE', - }, - ], - 'Rotate +90' - ), - _createActionButton( - 'flip-horizontal', - 'tool-flip-horizontal', - 'Flip Horizontally', - [ - { - commandName: 'flipViewportHorizontal', - commandOptions: {}, - context: 'CORNERSTONE', - }, - ], - 'Flip Horizontal' - ), - _createToggleButton('StackImageSync', 'link', 'Stack Image Sync', [ - { - commandName: 'toggleStackImageSync', - commandOptions: {}, - context: 'CORNERSTONE', - }, - ]), - _createToggleButton( - 'ReferenceLines', - 'tool-referenceLines', // change this with the new icon - 'Reference Lines', - [ - { - commandName: 'toggleReferenceLines', - commandOptions: {}, - context: 'CORNERSTONE', - }, - ] - ), - _createToolButton( - 'StackScroll', - 'tool-stack-scroll', - 'Stack Scroll', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'StackScroll', - }, - context: 'CORNERSTONE', - }, - ], - 'Stack Scroll' - ), - _createActionButton( - 'invert', - 'tool-invert', - 'Invert', - [ - { - commandName: 'invertViewport', - commandOptions: {}, - context: 'CORNERSTONE', - }, - ], - 'Invert Colors' - ), - _createToolButton( - 'Probe', - 'tool-probe', - 'Probe', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'DragProbe', - }, - context: 'CORNERSTONE', - }, - ], - 'Probe' - ), - _createToggleButton( - 'cine', - 'tool-cine', - 'Cine', - [ - { - commandName: 'toggleCine', - context: 'CORNERSTONE', - }, - ], - 'Cine' - ), - _createToolButton( - 'Angle', - 'tool-angle', - 'Angle', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'Angle', - }, - context: 'CORNERSTONE', - }, - ], - 'Angle' - ), - - // Next two tools can be added once icons are added - // _createToolButton( - // 'Cobb Angle', - // 'tool-cobb-angle', - // 'Cobb Angle', - // [ - // { - // commandName: 'setToolActive', - // commandOptions: { - // toolName: 'CobbAngle', - // }, - // context: 'CORNERSTONE', - // }, - // ], - // 'Cobb Angle' - // ), - // _createToolButton( - // 'Planar Freehand ROI', - // 'tool-freehand', - // 'PlanarFreehandROI', - // [ - // { - // commandName: 'setToolActive', - // commandOptions: { - // toolName: 'PlanarFreehandROI', - // }, - // context: 'CORNERSTONE', - // }, - // ], - // 'Planar Freehand ROI' - // ), - _createToolButton( - 'Magnify', - 'tool-magnify', - 'Magnify', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'Magnify', - }, - context: 'CORNERSTONE', - }, - ], - 'Magnify' - ), - _createToolButton( - 'Rectangle', - 'tool-rectangle', - 'Rectangle', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'RectangleROI', - }, - context: 'CORNERSTONE', - }, - ], - 'Rectangle' - ), - _createToolButton( - 'CalibrationLine', - 'tool-calibration', - 'Calibration', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'CalibrationLine', - }, - context: 'CORNERSTONE', - }, - ], - 'Calibration Line' - ), - _createActionButton( - 'TagBrowser', - 'list-bullets', - 'Dicom Tag Browser', - [ - { - commandName: 'openDICOMTagViewer', - commandOptions: {}, - context: 'DEFAULT', - }, - ], - 'Dicom Tag Browser' - ), - ], + evaluate: { + name: 'evaluate.cornerstoneTool', + disabledText: 'Select an MPR viewport to enable this tool', + }, }, }, ]; diff --git a/modes/cxr-mode/src/index.tsx b/modes/cxr-mode/src/index.tsx index 18b6556..364929e 100644 --- a/modes/cxr-mode/src/index.tsx +++ b/modes/cxr-mode/src/index.tsx @@ -1,5 +1,7 @@ import hotkeys from './hotkeyBindings.js'; +import i18n from 'i18next'; import toolbarButtons from './toolbarButtons.js'; +import moreTools from './moreTools.js'; import { id } from './id.js'; import initToolGroups from './initToolGroups.js'; import { DicomMetadataStore } from '@ohif/core'; @@ -47,10 +49,8 @@ async function customRouteInit({ studyInstanceUIDs, dataSource, }) { - const { - DisplaySetService, - HangingProtocolService, - } = servicesManager.services; + const { displaySetService, hangingProtocolService } = + servicesManager.services; const unsubscriptions = []; const { @@ -62,11 +62,11 @@ async function customRouteInit({ StudyInstanceUID, SeriesInstanceUID ); - DisplaySetService.makeDisplaySets(seriesMetadata.instances, madeInClient); + displaySetService.makeDisplaySets(seriesMetadata.instances, madeInClient); const studyMetadata = DicomMetadataStore.getStudy(StudyInstanceUID); if (!madeInClient) { - HangingProtocolService.run(studyMetadata); + hangingProtocolService.run(studyMetadata); } } ); @@ -91,53 +91,29 @@ const extensionDependencies = { }; function modeFactory({ modeConfiguration }) { + let _activatePanelTriggersSubscriptions = []; return { // TODO: We're using this as a route segment // We should not be. id, routeName: 'ngt_contour', - displayName: 'Nasogastric Tube Contour', + displayName: i18n.t('Nasogastric Tube Contour'), onModeEnter: ({ servicesManager, extensionManager, commandsManager }) => { - const { ToolBarService, ToolGroupService, GoogleSheetsService } = servicesManager.services; + const { + toolbarService, + toolGroupService, + GoogleSheetsService, + measurementService, + } = servicesManager.services; - GoogleSheetsService.init(); + measurementService.clearMeasurements(); // Init Default and SR ToolGroups - initToolGroups(extensionManager, ToolGroupService, commandsManager); - - let unsubscribe; - - const activateTool = () => { - ToolBarService.recordInteraction({ - groupId: 'WindowLevel', - itemId: 'WindowLevel', - interactionType: 'tool', - commands: [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'WindowLevel', - }, - context: 'CORNERSTONE', - }, - ], - }); - - // We don't need to reset the active tool whenever a viewport is getting - // added to the toolGroup. - unsubscribe(); - }; - - // Since we only have one viewport for the basic cs3d mode and it has - // only one hanging protocol, we can just use the first viewport - ({ unsubscribe } = ToolGroupService.subscribe( - ToolGroupService.EVENTS.VIEWPORT_ADDED, - activateTool - )); - - ToolBarService.init(extensionManager); - ToolBarService.addButtons(toolbarButtons); - ToolBarService.createButtonSection('primary', [ + initToolGroups(extensionManager, toolGroupService, commandsManager, this.labelConfig); + + GoogleSheetsService.init(); + toolbarService.addButtons([...toolbarButtons, ...moreTools]); + toolbarService.createButtonSection('primary', [ 'MeasurementTools', 'Zoom', 'WindowLevel', @@ -148,16 +124,25 @@ function modeFactory({ modeConfiguration }) { }, onModeExit: ({ servicesManager }) => { const { - ToolGroupService, - SyncGroupService, - MeasurementService, - ToolBarService, + toolGroupService, + syncGroupService, + measurementService, + toolbarService, + cornerstoneViewportService, + uiDialogService, + uiModalService, } = servicesManager.services; - ToolBarService.reset(); - MeasurementService.clearMeasurements(); - ToolGroupService.destroy(); - SyncGroupService.destroy(); + _activatePanelTriggersSubscriptions.forEach(sub => sub.unsubscribe()); + _activatePanelTriggersSubscriptions = []; + + uiDialogService.dismissAll(); + uiModalService.hide(); + toolbarService.reset(); + measurementService.clearMeasurements(); + toolGroupService.destroy(); + syncGroupService.destroy(); + cornerstoneViewportService.destroy(); }, validationTags: { study: [], @@ -167,7 +152,11 @@ function modeFactory({ modeConfiguration }) { const modalities_list = modalities.split('\\'); // Slide Microscopy modality not supported by basic mode yet - return !modalities_list.includes('SM'); + return { + valid: !modalities_list.includes('SM'), + description: + 'The mode does not support studies that ONLY include the following modalities: SM', + }; }, routes: [ { @@ -215,6 +204,7 @@ function modeFactory({ modeConfiguration }) { dicomsr.sopClassHandler, ], hotkeys: [...hotkeys], + ...modeConfiguration }; } @@ -225,3 +215,4 @@ const mode = { }; export default mode; +export { initToolGroups, moreTools, toolbarButtons }; diff --git a/modes/cxr-mode/src/initToolGroups.js b/modes/cxr-mode/src/initToolGroups.js index adb38da..a43a3b4 100644 --- a/modes/cxr-mode/src/initToolGroups.js +++ b/modes/cxr-mode/src/initToolGroups.js @@ -1,7 +1,9 @@ function initDefaultToolGroup( extensionManager, - ToolGroupService, - commandsManager + toolGroupService, + commandsManager, + toolGroupId, + modeLabelConfig ) { const utilityModule = extensionManager.getModuleEntry( '@ohif/extension-cornerstone.utilityModule.tools' @@ -28,45 +30,57 @@ function initDefaultToolGroup( passive: [ { toolName: toolNames.PlanarFreehandROI }, { toolName: toolNames.Length }, - { toolName: toolNames.ArrowAnnotate }, + { + toolName: toolNames.ArrowAnnotate, + configuration: { + getTextCallback: (callback, eventDetails) => { + if (modeLabelConfig) { + callback(' '); + } else { + commandsManager.runCommand('arrowTextCallback', { + callback, + eventDetails, + }); + } + }, + changeTextCallback: (data, eventDetails, callback) => { + if (modeLabelConfig === undefined) { + commandsManager.runCommand('arrowTextCallback', { + callback, + data, + eventDetails, + }); + } + }, + }, + }, { toolName: toolNames.Bidirectional }, { toolName: toolNames.DragProbe }, + { toolName: toolNames.Probe }, { toolName: toolNames.EllipticalROI }, + { toolName: toolNames.CircleROI }, { toolName: toolNames.RectangleROI }, { toolName: toolNames.StackScroll }, { toolName: toolNames.Angle }, + { toolName: toolNames.CobbAngle }, { toolName: toolNames.Magnify }, + { toolName: toolNames.SegmentationDisplay }, + { toolName: toolNames.CalibrationLine }, ], - // enabled - // disabled }; - const toolsConfig = { - [toolNames.ArrowAnnotate]: { - getTextCallback: (callback, eventDetails) => - commandsManager.runCommand('arrowTextCallback', { - callback, - eventDetails, - }), - - changeTextCallback: (data, eventDetails, callback) => - commandsManager.runCommand('arrowTextCallback', { - callback, - data, - eventDetails, - }), - }, - }; - - const toolGroupId = 'default'; - ToolGroupService.createToolGroupAndAddTools(toolGroupId, tools, toolsConfig); + toolGroupService.createToolGroupAndAddTools(toolGroupId, tools); } -function initSRToolGroup(extensionManager, ToolGroupService, commandsManager) { +function initSRToolGroup(extensionManager, toolGroupService, commandsManager) { const SRUtilityModule = extensionManager.getModuleEntry( '@ohif/extension-cornerstone-dicom-sr.utilityModule.tools' ); + if (!SRUtilityModule) { + return; + } + const CS3DUtilityModule = extensionManager.getModuleEntry( '@ohif/extension-cornerstone.utilityModule.tools' ); @@ -109,6 +123,9 @@ function initSRToolGroup(extensionManager, ToolGroupService, commandsManager) { { toolName: SRToolNames.SRArrowAnnotate }, { toolName: SRToolNames.SRBidirectional }, { toolName: SRToolNames.SREllipticalROI }, + { toolName: SRToolNames.SRCircleROI }, + { toolName: SRToolNames.SRPlanarFreehandROI }, + { toolName: SRToolNames.SRRectangleROI }, ], enabled: [ { @@ -119,30 +136,19 @@ function initSRToolGroup(extensionManager, ToolGroupService, commandsManager) { // disabled }; - const toolsConfig = { - [toolNames.ArrowAnnotate]: { - getTextCallback: (callback, eventDetails) => - commandsManager.runCommand('arrowTextCallback', { - callback, - eventDetails, - }), - - changeTextCallback: (data, eventDetails, callback) => - commandsManager.runCommand('arrowTextCallback', { - callback, - data, - eventDetails, - }), - }, - }; - const toolGroupId = 'SRToolGroup'; - ToolGroupService.createToolGroupAndAddTools(toolGroupId, tools, toolsConfig); + toolGroupService.createToolGroupAndAddTools(toolGroupId, tools); } -function initToolGroups(extensionManager, ToolGroupService, commandsManager) { - initDefaultToolGroup(extensionManager, ToolGroupService, commandsManager); - initSRToolGroup(extensionManager, ToolGroupService, commandsManager); +function initToolGroups(extensionManager, toolGroupService, commandsManager, modeLabelConfig) { + initDefaultToolGroup( + extensionManager, + toolGroupService, + commandsManager, + 'default', + modeLabelConfig + ); + initSRToolGroup(extensionManager, toolGroupService, commandsManager); } export default initToolGroups; diff --git a/modes/cxr-mode/src/moreTools.ts b/modes/cxr-mode/src/moreTools.ts new file mode 100644 index 0000000..ff03a83 --- /dev/null +++ b/modes/cxr-mode/src/moreTools.ts @@ -0,0 +1,187 @@ +import type { RunCommand } from '@ohif/core/types'; +import { EVENTS } from '@cornerstonejs/core'; +import { ToolbarService, ViewportGridService } from '@ohif/core'; +import { setToolActiveToolbar } from './toolbarButtons'; +const { createButton } = ToolbarService; + +const ReferenceLinesListeners: RunCommand = [ + { + commandName: 'setSourceViewportForReferenceLinesTool', + context: 'CORNERSTONE', + }, +]; + +const moreTools = [ + { + id: 'MoreTools', + uiType: 'ohif.splitButton', + props: { + groupId: 'MoreTools', + evaluate: + 'evaluate.group.promoteToPrimaryIfCornerstoneToolNotActiveInTheList', + primary: createButton({ + id: 'Reset', + icon: 'tool-reset', + tooltip: 'Reset View', + label: 'Reset', + commands: 'resetViewport', + evaluate: 'evaluate.action', + }), + secondary: { + icon: 'chevron-down', + label: '', + tooltip: 'More Tools', + }, + items: [ + createButton({ + id: 'Reset', + icon: 'tool-reset', + label: 'Reset View', + tooltip: 'Reset View', + commands: 'resetViewport', + evaluate: 'evaluate.action', + }), + createButton({ + id: 'rotate-right', + icon: 'tool-rotate-right', + label: 'Rotate Right', + tooltip: 'Rotate +90', + commands: 'rotateViewportCW', + evaluate: 'evaluate.action', + }), + createButton({ + id: 'flipHorizontal', + icon: 'tool-flip-horizontal', + label: 'Flip Horizontal', + tooltip: 'Flip Horizontally', + commands: 'flipViewportHorizontal', + evaluate: ['evaluate.viewportProperties.toggle', 'evaluate.not3D'], + }), + /* + createButton({ + id: 'ImageSliceSync', + icon: 'link', + label: 'Image Slice Sync', + tooltip: 'Enable position synchronization on stack viewports', + commands: { + commandName: 'toggleSynchronizer', + commandOptions: { + type: 'imageSlice', + }, + }, + listeners: { + [EVENTS.STACK_VIEWPORT_NEW_STACK]: { + commandName: 'toggleImageSliceSync', + commandOptions: { toggledState: true }, + }, + }, + evaluate: ['evaluate.cornerstone.synchronizer', 'evaluate.not3D'], + }), + createButton({ + id: 'ReferenceLines', + icon: 'tool-referenceLines', + label: 'Reference Lines', + tooltip: 'Show Reference Lines', + commands: 'toggleEnabledDisabledToolbar', + listeners: { + [ViewportGridService.EVENTS.ACTIVE_VIEWPORT_ID_CHANGED]: ReferenceLinesListeners, + [ViewportGridService.EVENTS.VIEWPORTS_READY]: ReferenceLinesListeners, + }, + evaluate: 'evaluate.cornerstoneTool.toggle', + }), + createButton({ + id: 'ImageOverlayViewer', + icon: 'toggle-dicom-overlay', + label: 'Image Overlay', + tooltip: 'Toggle Image Overlay', + commands: 'toggleEnabledDisabledToolbar', + evaluate: 'evaluate.cornerstoneTool.toggle', + }), + */ + createButton({ + id: 'StackScroll', + icon: 'tool-stack-scroll', + label: 'Stack Scroll', + tooltip: 'Stack Scroll', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + createButton({ + id: 'invert', + icon: 'tool-invert', + label: 'Invert', + tooltip: 'Invert Colors', + commands: 'invertViewport', + evaluate: 'evaluate.viewportProperties.toggle', + }), + createButton({ + id: 'Probe', + icon: 'tool-probe', + label: 'Probe', + tooltip: 'Probe', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + createButton({ + id: 'Cine', + icon: 'tool-cine', + label: 'Cine', + tooltip: 'Cine', + commands: 'toggleCine', + evaluate: ['evaluate.cine', 'evaluate.not3D'], + }), + createButton({ + id: 'Angle', + icon: 'tool-angle', + label: 'Angle', + tooltip: 'Angle', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + createButton({ + id: 'Magnify', + icon: 'tool-magnify', + label: 'Zoom-in', + tooltip: 'Zoom-in', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + /* + createButton({ + id: 'CalibrationLine', + icon: 'tool-calibration', + label: 'Calibration', + tooltip: 'Calibration Line', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + createButton({ + id: 'TagBrowser', + icon: 'dicom-tag-browser', + label: 'Dicom Tag Browser', + tooltip: 'Dicom Tag Browser', + commands: 'openDICOMTagViewer', + }), + createButton({ + id: 'AdvancedMagnify', + icon: 'icon-tool-loupe', + label: 'Magnify Probe', + tooltip: 'Magnify Probe', + commands: 'toggleActiveDisabledToolbar', + evaluate: 'evaluate.cornerstoneTool.toggle.ifStrictlyDisabled', + }), + createButton({ + id: 'UltrasoundDirectionalTool', + icon: 'icon-tool-ultrasound-bidirectional', + label: 'Ultrasound Directional', + tooltip: 'Ultrasound Directional', + commands: setToolActiveToolbar, + evaluate: ['evaluate.cornerstoneTool', 'evaluate.isUS'], + }), + */ + ], + }, + }, +]; + +export default moreTools; diff --git a/modes/cxr-mode/src/toolbarButtons.js b/modes/cxr-mode/src/toolbarButtons.js index 1c60e59..e21efe1 100644 --- a/modes/cxr-mode/src/toolbarButtons.js +++ b/modes/cxr-mode/src/toolbarButtons.js @@ -1,466 +1,208 @@ // TODO: torn, can either bake this here; or have to create a whole new button type // Only ways that you can pass in a custom React component for render :l -import { - // ExpandableToolbarButton, - // ListMenu, - WindowLevelMenuItem, -} from '@ohif/ui'; -import { defaults } from '@ohif/core'; +import { ToolbarService } from '@ohif/core'; -const { windowLevelPresets } = defaults; -/** - * - * @param {*} type - 'tool' | 'action' | 'toggle' - * @param {*} id - * @param {*} icon - * @param {*} label - */ -function _createButton(type, id, icon, label, commands, tooltip) { - return { - id, - icon, - label, - type, - commands, - tooltip, - }; -} +const { createButton } = ToolbarService; -const _createActionButton = _createButton.bind(null, 'action'); -const _createToggleButton = _createButton.bind(null, 'toggle'); -const _createToolButton = _createButton.bind(null, 'tool'); - -/** - * - * @param {*} preset - preset number (from above import) - * @param {*} title - * @param {*} subtitle - */ -function _createWwwcPreset(preset, title, subtitle) { - return { - id: preset.toString(), - title, - subtitle, - type: 'action', - commands: [ - { - commandName: 'setWindowLevel', - commandOptions: { - ...windowLevelPresets[preset], - }, - context: 'CORNERSTONE', - }, - ], - }; -} +export const setToolActiveToolbar = { + commandName: 'setToolActiveToolbar', + commandOptions: { + toolGroupIds: ['default', 'mpr', 'SRToolGroup', 'volume3d'], + }, +}; const toolbarButtons = [ - // Measurement + // Measurement Tools { id: 'MeasurementTools', - type: 'ohif.splitButton', + uiType: 'ohif.splitButton', props: { groupId: 'MeasurementTools', - isRadio: true, // ? - // Switch? - primary: _createToolButton( - 'PlanarFreehandROI', - 'pencil', - 'FreehandROI', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'PlanarFreehandROI', - }, - context: 'CORNERSTONE', - }, - ], - 'FreehandROI Tool' - ), + // group evaluate to determine which item should move to the top + evaluate: + 'evaluate.group.promoteToPrimaryIfCornerstoneToolNotActiveInTheList', + primary: createButton({ + id: 'PlanarFreehandROI', + icon: 'icon-tool-freehand-roi', + label: 'Freehand ROI', + tooltip: 'Freehand ROI', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), secondary: { icon: 'chevron-down', - label: '', - isActive: true, tooltip: 'More Measure Tools', }, items: [ - _createToolButton( - 'Length', - 'tool-length', - 'Length', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'Length', - }, - context: 'CORNERSTONE', - }, - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'SRLength', - toolGroupId: 'SRToolGroup', - }, - // we can use the setToolActive command for this from Cornerstone commandsModule - context: 'CORNERSTONE', - }, - ], - 'Length Tool' - ), - _createToolButton( - 'Bidirectional', - 'tool-bidirectional', - 'Bidirectional', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'Bidirectional', - }, - context: 'CORNERSTONE', - }, - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'SRBidirectional', - toolGroupId: 'SRToolGroup', - }, - context: 'CORNERSTONE', - }, - ], - 'Bidirectional Tool' - ), - _createToolButton( - 'ArrowAnnotate', - 'tool-annotate', - 'Annotation', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'ArrowAnnotate', - }, - context: 'CORNERSTONE', - }, - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'SRArrowAnnotate', - toolGroupId: 'SRToolGroup', - }, - context: 'CORNERSTONE', - }, - ], - 'Arrow Annotate' - ), - _createToolButton( - 'EllipticalROI', - 'tool-elipse', - 'Ellipse', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'EllipticalROI', - }, - context: 'CORNERSTONE', - }, - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'SREllipticalROI', - toolGroupId: 'SRToolGroup', - }, - context: 'CORNERSTONE', - }, - ], - 'Ellipse Tool' - ), - _createToolButton( - 'PlanarFreehandROI', - 'pencil', - 'FreehandROI', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'PlanarFreehandROI', - }, - context: 'CORNERSTONE', - }, - ], - 'FreehandROI Tool' - ), + createButton({ + id: 'Length', + icon: 'tool-length', + label: 'Length', + tooltip: 'Length Tool', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + createButton({ + id: 'Bidirectional', + icon: 'tool-bidirectional', + label: 'Bidirectional', + tooltip: 'Bidirectional Tool', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + createButton({ + id: 'ArrowAnnotate', + icon: 'tool-annotate', + label: 'Annotation', + tooltip: 'Arrow Annotate', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + createButton({ + id: 'EllipticalROI', + icon: 'tool-ellipse', + label: 'Ellipse', + tooltip: 'Ellipse ROI', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + createButton({ + id: 'RectangleROI', + icon: 'tool-rectangle', + label: 'Rectangle', + tooltip: 'Rectangle ROI', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + /* + createButton({ + id: 'CircleROI', + icon: 'tool-circle', + label: 'Circle', + tooltip: 'Circle Tool', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + */ + createButton({ + id: 'PlanarFreehandROI', + icon: 'icon-tool-freehand-roi', + label: 'Freehand ROI', + tooltip: 'Freehand ROI', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + /* + createButton({ + id: 'SplineROI', + icon: 'icon-tool-spline-roi', + label: 'Spline ROI', + tooltip: 'Spline ROI', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + createButton({ + id: 'LivewireContour', + icon: 'icon-tool-livewire', + label: 'Livewire tool', + tooltip: 'Livewire tool', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), + */ ], }, }, // Zoom.. { id: 'Zoom', - type: 'ohif.radioGroup', + uiType: 'ohif.radioGroup', props: { - type: 'tool', icon: 'tool-zoom', label: 'Zoom', - commands: [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'Zoom', - }, - context: 'CORNERSTONE', - }, - ], + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', }, }, - // Window Level + Presets... + // Window Level { id: 'WindowLevel', - type: 'ohif.splitButton', + uiType: 'ohif.radioGroup', props: { - groupId: 'WindowLevel', - primary: _createToolButton( - 'WindowLevel', - 'tool-window-level', - 'Window Level', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'WindowLevel', - }, - context: 'CORNERSTONE', - }, - ], - 'Window Level' - ), - secondary: { - icon: 'chevron-down', - label: 'W/L Manual', - isActive: true, - tooltip: 'W/L Presets', - }, - isAction: true, // ? - renderer: WindowLevelMenuItem, - items: [ - _createWwwcPreset(1, 'Soft tissue', '400 / 40'), - _createWwwcPreset(2, 'Lung', '1500 / -600'), - _createWwwcPreset(3, 'Liver', '150 / 90'), - _createWwwcPreset(4, 'Bone', '2500 / 480'), - _createWwwcPreset(5, 'Brain', '80 / 40'), - ], + icon: 'tool-window-level', + label: 'Window Level', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', }, }, // Pan... { id: 'Pan', - type: 'ohif.radioGroup', + uiType: 'ohif.radioGroup', props: { type: 'tool', icon: 'tool-move', label: 'Pan', - commands: [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'Pan', - }, - context: 'CORNERSTONE', - }, - ], + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', }, }, + /* + { + id: 'TrackballRotate', + uiType: 'ohif.radioGroup', + props: { + type: 'tool', + icon: 'tool-3d-rotate', + label: '3D Rotate', + commands: setToolActiveToolbar, + evaluate: { + name: 'evaluate.cornerstoneTool', + disabledText: 'Select a 3D viewport to enable this tool', + }, + }, + }, + */ { id: 'Capture', - type: 'ohif.action', + uiType: 'ohif.radioGroup', props: { icon: 'tool-capture', label: 'Capture', - type: 'action', - commands: [ - { - commandName: 'showDownloadViewportModal', - commandOptions: {}, - context: 'CORNERSTONE', - }, - ], + commands: 'showDownloadViewportModal', + evaluate: 'evaluate.action', }, }, { id: 'Layout', - type: 'ohif.layoutSelector', + uiType: 'ohif.layoutSelector', + props: { + rows: 3, + columns: 4, + evaluate: 'evaluate.action', + }, }, - // More... + /* { - id: 'MoreTools', - type: 'ohif.splitButton', + id: 'Crosshairs', + uiType: 'ohif.radioGroup', props: { - isRadio: true, // ? - groupId: 'MoreTools', - primary: _createActionButton( - 'Reset', - 'tool-reset', - 'Reset View', - [ - { - commandName: 'resetViewport', - commandOptions: {}, - context: 'CORNERSTONE', - }, - ], - 'Reset' - ), - secondary: { - icon: 'chevron-down', - label: '', - isActive: true, - tooltip: 'More Tools', + type: 'tool', + icon: 'tool-crosshair', + label: 'Crosshairs', + commands: { + commandName: 'setToolActiveToolbar', + commandOptions: { + toolGroupIds: ['mpr'], + }, + }, + evaluate: { + name: 'evaluate.cornerstoneTool', + disabledText: 'Select an MPR viewport to enable this tool', }, - items: [ - _createActionButton( - 'Reset', - 'tool-reset', - 'Reset View', - [ - { - commandName: 'resetViewport', - commandOptions: {}, - context: 'CORNERSTONE', - }, - ], - 'Reset' - ), - _createActionButton( - 'rotate-right', - 'tool-rotate-right', - 'Rotate Right', - [ - { - commandName: 'rotateViewportCW', - commandOptions: {}, - context: 'CORNERSTONE', - }, - ], - 'Rotate +90' - ), - _createActionButton( - 'flip-horizontal', - 'tool-flip-horizontal', - 'Flip Horizontally', - [ - { - commandName: 'flipViewportHorizontal', - commandOptions: {}, - context: 'CORNERSTONE', - }, - ], - 'Flip Horizontal' - ), - _createToolButton( - 'StackScroll', - 'tool-stack-scroll', - 'Stack Scroll', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'StackScroll', - }, - context: 'CORNERSTONE', - }, - ], - 'Stack Scroll' - ), - _createActionButton( - 'invert', - 'tool-invert', - 'Invert', - [ - { - commandName: 'invertViewport', - commandOptions: {}, - context: 'CORNERSTONE', - }, - ], - 'Invert Colors' - ), - _createToolButton( - 'Probe', - 'tool-probe', - 'Probe', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'DragProbe', - }, - context: 'CORNERSTONE', - }, - ], - 'Probe' - ), - _createToggleButton( - 'cine', - 'tool-cine', - 'Cine', - [ - { - commandName: 'toggleCine', - context: 'CORNERSTONE', - }, - ], - 'Cine' - ), - _createToolButton( - 'Angle', - 'tool-angle', - 'Angle', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'Angle', - }, - context: 'CORNERSTONE', - }, - ], - 'Angle' - ), - _createToolButton( - 'Magnify', - 'tool-magnify', - 'Magnify', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'Magnify', - }, - context: 'CORNERSTONE', - }, - ], - 'Magnify' - ), - _createToolButton( - 'Rectangle', - 'tool-rectangle', - 'Rectangle', - [ - { - commandName: 'setToolActive', - commandOptions: { - toolName: 'RectangleROI', - }, - context: 'CORNERSTONE', - }, - ], - 'Rectangle' - ), - ], }, }, + */ ]; export default toolbarButtons;