diff --git a/extensions/cornerstone-dicom-rt/src/utils/promptHydrateRT.ts b/extensions/cornerstone-dicom-rt/src/utils/promptHydrateRT.ts index 2baa0305b..5a095c116 100644 --- a/extensions/cornerstone-dicom-rt/src/utils/promptHydrateRT.ts +++ b/extensions/cornerstone-dicom-rt/src/utils/promptHydrateRT.ts @@ -15,23 +15,28 @@ function promptHydrateRT({ hydrateRTDisplaySet, }) { const { uiViewportDialogService } = servicesManager.services; - + const extensionManager = servicesManager._extensionManager; + const appConfig = extensionManager._appConfig; return new Promise(async function (resolve, reject) { - const promptResult = await _askHydrate(uiViewportDialogService, viewportId); + const promptResult = appConfig?.disableConfirmationPrompts + ? RESPONSE.HYDRATE_SEG + : await _askHydrate(uiViewportDialogService, viewportId); if (promptResult === RESPONSE.HYDRATE_SEG) { preHydrateCallbacks?.forEach(callback => { callback(); }); - const isHydrated = await hydrateRTDisplaySet({ - rtDisplaySet, - viewportId, - toolGroupId, - servicesManager, - }); + window.setTimeout(async () => { + const isHydrated = await hydrateRTDisplaySet({ + rtDisplaySet, + viewportId, + toolGroupId, + servicesManager, + }); - resolve(isHydrated); + resolve(isHydrated); + }, 0); } }); } diff --git a/extensions/cornerstone-dicom-seg/src/commandsModule.ts b/extensions/cornerstone-dicom-seg/src/commandsModule.ts index 4a770d7e1..e43065f6c 100644 --- a/extensions/cornerstone-dicom-seg/src/commandsModule.ts +++ b/extensions/cornerstone-dicom-seg/src/commandsModule.ts @@ -258,9 +258,12 @@ const commandsModule = ({ labelmapObj.metadata = []; const segmentationInOHIF = segmentationService.getSegmentation(segmentationId); - labelmapObj.segmentsOnLabelmap.forEach(segmentIndex => { + segmentationInOHIF.segments.forEach(segment => { // segmentation service already has a color for each segment - const segment = segmentationInOHIF?.segments[segmentIndex]; + if (!segment) { + return; + } + const segmentIndex = segment.segmentIndex; const { label, color } = segment; const RecommendedDisplayCIELabValue = dcmjs.data.Colors.rgb2DICOMLAB( diff --git a/extensions/cornerstone-dicom-seg/src/utils/promptHydrateSEG.ts b/extensions/cornerstone-dicom-seg/src/utils/promptHydrateSEG.ts index c42b32e69..9bd7ef66d 100644 --- a/extensions/cornerstone-dicom-seg/src/utils/promptHydrateSEG.ts +++ b/extensions/cornerstone-dicom-seg/src/utils/promptHydrateSEG.ts @@ -14,21 +14,27 @@ function promptHydrateSEG({ hydrateSEGDisplaySet, }) { const { uiViewportDialogService } = servicesManager.services; + const extensionManager = servicesManager._extensionManager; + const appConfig = extensionManager._appConfig; return new Promise(async function (resolve, reject) { - const promptResult = await _askHydrate(uiViewportDialogService, viewportId); + const promptResult = appConfig?.disableConfirmationPrompts + ? RESPONSE.HYDRATE_SEG + : await _askHydrate(uiViewportDialogService, viewportId); if (promptResult === RESPONSE.HYDRATE_SEG) { preHydrateCallbacks?.forEach(callback => { callback(); }); - const isHydrated = await hydrateSEGDisplaySet({ - segDisplaySet, - viewportId, - }); + window.setTimeout(async () => { + const isHydrated = await hydrateSEGDisplaySet({ + segDisplaySet, + viewportId, + }); - resolve(isHydrated); + resolve(isHydrated); + }, 0); } }); } diff --git a/extensions/cornerstone/src/Viewport/Overlays/CustomizableViewportOverlay.tsx b/extensions/cornerstone/src/Viewport/Overlays/CustomizableViewportOverlay.tsx index 19647b732..eb8e834c2 100644 --- a/extensions/cornerstone/src/Viewport/Overlays/CustomizableViewportOverlay.tsx +++ b/extensions/cornerstone/src/Viewport/Overlays/CustomizableViewportOverlay.tsx @@ -137,20 +137,8 @@ function CustomizableViewportOverlay({ return; } - const imageData = viewport.getImageData(); + const scale = viewport.getZoom(); - if (!imageData) { - return; - } - - if (camera.scale) { - setScale(camera.scale); - return; - } - - const { spacing } = imageData; - // convert parallel scale to scale - const scale = (element.clientHeight * spacing[0] * 0.5) / camera.parallelScale; setScale(scale); } }; diff --git a/extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts b/extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts index 355bcd0f5..87b2590ee 100644 --- a/extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts +++ b/extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts @@ -568,7 +568,7 @@ class SegmentationService extends PubSubService { rgba, } = segmentInfo; - const { x, y, z } = segDisplaySet.centroids.get(segmentIndex); + const { x, y, z } = segDisplaySet.centroids.get(segmentIndex) || { x: 0, y: 0, z: 0 }; const centerWorld = derivedVolume.imageData.indexToWorld([x, y, z]); segmentation.cachedStats = { diff --git a/extensions/default/src/Actions/createReportAsync.tsx b/extensions/default/src/Actions/createReportAsync.tsx index d0f34a48a..6248421e4 100644 --- a/extensions/default/src/Actions/createReportAsync.tsx +++ b/extensions/default/src/Actions/createReportAsync.tsx @@ -39,6 +39,7 @@ async function createReportAsync({ servicesManager, getReport, reportType = 'mea message: error.message || `Failed to store ${reportType}`, type: 'error', }); + throw new Error(`Failed to store ${reportType}. Error: ${error.message || 'Unknown error'}`); } finally { uiDialogService.dismiss({ id: loadingDialogId }); } diff --git a/extensions/dicom-microscopy/src/index.tsx b/extensions/dicom-microscopy/src/index.tsx index bd26b5c3e..ff36bde14 100644 --- a/extensions/dicom-microscopy/src/index.tsx +++ b/extensions/dicom-microscopy/src/index.tsx @@ -99,7 +99,9 @@ const extension: Types.Extensions.Extension = { const { microscopyService } = servicesManager.services; const activeInteractions = microscopyService.getActiveInteractions(); - + if (!activeInteractions) { + return false; + } const isPrimaryActive = activeInteractions.find(interactions => { const sameMouseButton = interactions[1].bindings.mouseButtons.includes('left'); diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptBeginTracking.js b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptBeginTracking.js index d8bb02b99..2638206e6 100644 --- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptBeginTracking.js +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptBeginTracking.js @@ -11,12 +11,15 @@ const RESPONSE = { function promptBeginTracking({ servicesManager, extensionManager }, ctx, evt) { const { uiViewportDialogService } = servicesManager.services; + const appConfig = extensionManager._appConfig; // When the state change happens after a promise, the state machine sends the retult in evt.data; // In case of direct transition to the state, the state machine sends the data in evt; const { viewportId, StudyInstanceUID, SeriesInstanceUID } = evt.data || evt; return new Promise(async function (resolve, reject) { - let promptResult = await _askTrackMeasurements(uiViewportDialogService, viewportId); + let promptResult = appConfig?.disableConfirmationPrompts + ? RESPONSE.SET_STUDY_AND_SERIES + : await _askTrackMeasurements(uiViewportDialogService, viewportId); resolve({ userResponse: promptResult, diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptHydrateStructuredReport.js b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptHydrateStructuredReport.js index 579ff9c7b..c12135a4d 100644 --- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptHydrateStructuredReport.js +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptHydrateStructuredReport.js @@ -15,9 +15,10 @@ function promptHydrateStructuredReport({ servicesManager, extensionManager, appC const { uiViewportDialogService, displaySetService } = servicesManager.services; const { viewportId, displaySetInstanceUID } = evt; const srDisplaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID); - return new Promise(async function (resolve, reject) { - const promptResult = await _askTrackMeasurements(uiViewportDialogService, viewportId); + const promptResult = appConfig?.disableConfirmationPrompts + ? RESPONSE.HYDRATE_REPORT + : await _askTrackMeasurements(uiViewportDialogService, viewportId); // Need to do action here... So we can set state... let StudyInstanceUID, SeriesInstanceUIDs; diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptSaveReport.js b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptSaveReport.js index 1719c56dd..fd2d69872 100644 --- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptSaveReport.js +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptSaveReport.js @@ -2,7 +2,7 @@ import { createReportAsync, createReportDialogPrompt } from '@ohif/extension-def import getNextSRSeriesNumber from '../../_shared/getNextSRSeriesNumber'; import RESPONSE from '../../_shared/PROMPT_RESPONSES'; -function promptSaveReport({ servicesManager, commandsManager, extensionManager }, ctx, evt) { +async function promptSaveReport({ servicesManager, commandsManager, extensionManager }, ctx, evt) { const { uiDialogService, measurementService, displaySetService } = servicesManager.services; const viewportId = evt.viewportId === undefined ? evt.data.viewportId : evt.viewportId; const isBackupSave = evt.isBackupSave === undefined ? evt.data.isBackupSave : evt.isBackupSave; @@ -12,8 +12,7 @@ function promptSaveReport({ servicesManager, commandsManager, extensionManager } const { trackedStudy, trackedSeries } = ctx; let displaySetInstanceUIDs; - return new Promise(async function (resolve, reject) { - // TODO: Fallback if (uiDialogService) { + try { const promptResult = await createReportDialogPrompt(uiDialogService, { extensionManager, }); @@ -57,15 +56,17 @@ function promptSaveReport({ servicesManager, commandsManager, extensionManager } // Do nothing } - resolve({ + return { userResponse: promptResult.action, createdDisplaySetInstanceUIDs: displaySetInstanceUIDs, StudyInstanceUID, SeriesInstanceUID, viewportId, isBackupSave, - }); - }); + }; + } catch (error) { + return null; + } } export default promptSaveReport; diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptTrackNewStudy.js b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptTrackNewStudy.js index b590af3f6..48762f72e 100644 --- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptTrackNewStudy.js +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptTrackNewStudy.js @@ -11,12 +11,15 @@ const RESPONSE = { function promptTrackNewStudy({ servicesManager, extensionManager }, ctx, evt) { const { UIViewportDialogService } = servicesManager.services; + const appConfig = extensionManager._appConfig; // When the state change happens after a promise, the state machine sends the retult in evt.data; // In case of direct transition to the state, the state machine sends the data in evt; const { viewportId, StudyInstanceUID, SeriesInstanceUID } = evt.data || evt; return new Promise(async function (resolve, reject) { - let promptResult = await _askTrackMeasurements(UIViewportDialogService, viewportId); + let promptResult = appConfig?.disableConfirmationPrompts + ? RESPONSE.SET_STUDY_AND_SERIES + : await _askTrackMeasurements(UIViewportDialogService, viewportId); if (promptResult === RESPONSE.SET_STUDY_AND_SERIES) { promptResult = ctx.isDirty diff --git a/modes/basic-test-mode/src/initToolGroups.ts b/modes/basic-test-mode/src/initToolGroups.ts index db956dd55..29cb8f4fd 100644 --- a/modes/basic-test-mode/src/initToolGroups.ts +++ b/modes/basic-test-mode/src/initToolGroups.ts @@ -1,3 +1,15 @@ +const colours = { + 'viewport-0': 'rgb(200, 0, 0)', + 'viewport-1': 'rgb(200, 200, 0)', + 'viewport-2': 'rgb(0, 200, 0)', +}; + +const colorsByOrientation = { + axial: 'rgb(200, 0, 0)', + sagittal: 'rgb(200, 200, 0)', + coronal: 'rgb(0, 200, 0)', +}; + function initDefaultToolGroup(extensionManager, toolGroupService, commandsManager, toolGroupId) { const utilityModule = extensionManager.getModuleEntry( '@ohif/extension-cornerstone.utilityModule.tools' @@ -132,6 +144,9 @@ function initMPRToolGroup(extensionManager, toolGroupService, commandsManager) { '@ohif/extension-cornerstone.utilityModule.tools' ); + const serviceManager = extensionManager._servicesManager; + const { cornerstoneViewportService } = serviceManager.services; + const { toolNames, Enums } = utilityModule.exports; const tools = { @@ -188,6 +203,20 @@ function initMPRToolGroup(extensionManager, toolGroupService, commandsManager) { enabled: false, panSize: 10, }, + getReferenceLineColor: viewportId => { + const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId); + const viewportOptions = viewportInfo?.viewportOptions; + if (viewportOptions) { + return ( + colours[viewportOptions.id] || + colorsByOrientation[viewportOptions.orientation] || + '#0c0' + ); + } else { + console.warn('missing viewport?', viewportId); + return '#0c0'; + } + }, }, }, { toolName: toolNames.ReferenceLines }, diff --git a/modes/basic-test-mode/src/moreTools.ts b/modes/basic-test-mode/src/moreTools.ts index 98ef45de6..d23ce41ea 100644 --- a/modes/basic-test-mode/src/moreTools.ts +++ b/modes/basic-test-mode/src/moreTools.ts @@ -135,6 +135,14 @@ const moreTools = [ commands: setToolActiveToolbar, evaluate: 'evaluate.cornerstoneTool', }), + createButton({ + id: 'CobbAngle', + icon: 'tool-angle', + label: 'Cobb Angle', + tooltip: 'Cobb Angle', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), createButton({ id: 'Magnify', icon: 'tool-magnify', diff --git a/modes/longitudinal/src/initToolGroups.js b/modes/longitudinal/src/initToolGroups.js index 983f1f56a..02bbe1a67 100644 --- a/modes/longitudinal/src/initToolGroups.js +++ b/modes/longitudinal/src/initToolGroups.js @@ -1,3 +1,15 @@ +const colours = { + 'viewport-0': 'rgb(200, 0, 0)', + 'viewport-1': 'rgb(200, 200, 0)', + 'viewport-2': 'rgb(0, 200, 0)', +}; + +const colorsByOrientation = { + axial: 'rgb(200, 0, 0)', + sagittal: 'rgb(200, 200, 0)', + coronal: 'rgb(0, 200, 0)', +}; + function initDefaultToolGroup( extensionManager, toolGroupService, @@ -155,6 +167,9 @@ function initMPRToolGroup(extensionManager, toolGroupService, commandsManager, m '@ohif/extension-cornerstone.utilityModule.tools' ); + const serviceManager = extensionManager._servicesManager; + const { cornerstoneViewportService } = serviceManager.services; + const { toolNames, Enums } = utilityModule.exports; const tools = { @@ -221,6 +236,20 @@ function initMPRToolGroup(extensionManager, toolGroupService, commandsManager, m enabled: false, panSize: 10, }, + getReferenceLineColor: viewportId => { + const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId); + const viewportOptions = viewportInfo?.viewportOptions; + if (viewportOptions) { + return ( + colours[viewportOptions.id] || + colorsByOrientation[viewportOptions.orientation] || + '#0c0' + ); + } else { + console.warn('missing viewport?', viewportId); + return '#0c0'; + } + }, }, }, { diff --git a/modes/longitudinal/src/moreTools.ts b/modes/longitudinal/src/moreTools.ts index 0550ef839..120e0f403 100644 --- a/modes/longitudinal/src/moreTools.ts +++ b/modes/longitudinal/src/moreTools.ts @@ -135,6 +135,14 @@ const moreTools = [ commands: setToolActiveToolbar, evaluate: 'evaluate.cornerstoneTool', }), + createButton({ + id: 'CobbAngle', + icon: 'tool-angle', + label: 'Cobb Angle', + tooltip: 'Cobb Angle', + commands: setToolActiveToolbar, + evaluate: 'evaluate.cornerstoneTool', + }), createButton({ id: 'Magnify', icon: 'tool-magnify', diff --git a/modes/microscopy/src/index.tsx b/modes/microscopy/src/index.tsx index 5c0bdba80..42bf3f536 100644 --- a/modes/microscopy/src/index.tsx +++ b/modes/microscopy/src/index.tsx @@ -51,7 +51,7 @@ function modeFactory({ modeConfiguration }) { const { toolbarService } = servicesManager.services; toolbarService.addButtons(toolbarButtons); - toolbarService.createButtonSection('primary', ['MeasurementTools', 'dragPan']); + toolbarService.createButtonSection('primary', ['MeasurementTools', 'dragPan', 'TagBrowser']); }, onModeExit: ({ servicesManager }) => { diff --git a/modes/microscopy/src/toolbarButtons.js b/modes/microscopy/src/toolbarButtons.js index 9954089d7..82c774683 100644 --- a/modes/microscopy/src/toolbarButtons.js +++ b/modes/microscopy/src/toolbarButtons.js @@ -145,6 +145,20 @@ const toolbarButtons = [ evaluate: 'evaluate.microscopyTool', }, }, + { + id: 'TagBrowser', + uiType: 'ohif.radioGroup', + props: { + icon: 'dicom-tag-browser', + label: 'Dicom Tag Browser', + commands: [ + { + commandName: 'openDICOMTagViewer', + }, + ], + evaluate: 'evaluate.action', + }, + }, ]; export default toolbarButtons; diff --git a/platform/docs/docs/configuration/configurationFiles.md b/platform/docs/docs/configuration/configurationFiles.md index 1c945f4cb..8201c0905 100644 --- a/platform/docs/docs/configuration/configurationFiles.md +++ b/platform/docs/docs/configuration/configurationFiles.md @@ -124,6 +124,7 @@ Here are a list of some options available: - `acceptHeader` : accept header to request specific dicom transfer syntax ex : [ 'multipart/related; type=image/jls; q=1', 'multipart/related; type=application/octet-stream; q=0.1' ] - `investigationalUseDialog`: This should contain an object with `option` value, it can be either `always` which always shows the dialog once per session, `never` which never shows the dialog, or `configure` which shows the dialog once and won't show it again until a set number of days defined by the user, if it's set to configure, you are required to add an additional property `days` which is the number of days to wait before showing the dialog again. - `groupEnabledModesFirst`: boolean, if set to true, all valid modes for the study get grouped together first, then the rest of the modes. If false, all modes are shown in the order they are defined in the configuration. +- `disableConfirmationPrompts`: boolean, if set to true, it skips confirmation prompts for measurement tracking and hydration. - `showPatientInfo`: string, if set to 'visible', the patient info header will be shown and its initial state is expanded. If set to 'visibleCollapsed', the patient info header will be shown but it's initial state is collapsed. If set to 'disabled', the patient info header will never be shown, and if set to 'visibleReadOnly', the patient info header will be shown and always expanded. - `requestTransferSyntaxUID` : Request a specific Transfer syntax from dicom web server ex: 1.2.840.10008.1.2.4.80 (applied only if acceptHeader is not set) - `omitQuotationForMultipartRequest`: Some servers (e.g., .NET) require the `multipart/related` request to be sent without quotation marks. Defaults to `false`. If your server doesn't require this, then setting this flag to `true` might improve performance (by removing the need for preflight requests). Also note that diff --git a/platform/ui/src/components/Select/Select.tsx b/platform/ui/src/components/Select/Select.tsx index a24a00c69..c15e000a2 100644 --- a/platform/ui/src/components/Select/Select.tsx +++ b/platform/ui/src/components/Select/Select.tsx @@ -94,6 +94,7 @@ const Select = ({ components={_components} placeholder={placeholder} options={options} + blurInputOnSelect={true} value={value && Array.isArray(value) ? selectedOptions : value} onChange={(selectedOptions, { action }) => { const newSelection = !selectedOptions.length