diff --git a/.gitignore b/.gitignore index 63198db48..f2c4c6a19 100644 --- a/.gitignore +++ b/.gitignore @@ -66,3 +66,8 @@ libs/ # Backup files *~ + +# cornerstone3D local linking +libs/ +link-cs3d.js +unlink-cs3d.js diff --git a/extensions/cornerstone-dicom-seg/src/commandsModule.ts b/extensions/cornerstone-dicom-seg/src/commandsModule.ts index 6f300e10b..1243f6f24 100644 --- a/extensions/cornerstone-dicom-seg/src/commandsModule.ts +++ b/extensions/cornerstone-dicom-seg/src/commandsModule.ts @@ -2,9 +2,8 @@ import dcmjs from 'dcmjs'; import { classes, Types, utils } from '@ohif/core'; import { cache, metaData } from '@cornerstonejs/core'; import { segmentation as cornerstoneToolsSegmentation } from '@cornerstonejs/tools'; -import { adaptersRT, helpers, adaptersSEG } from '@cornerstonejs/adapters'; +import { adaptersRT, adaptersSEG } from '@cornerstonejs/adapters'; import { createReportDialogPrompt, useUIStateStore } from '@ohif/extension-default'; -import { DicomMetadataStore } from '@ohif/core'; import PROMPT_RESPONSES from '../../default/src/utils/_shared/PROMPT_RESPONSES'; @@ -29,11 +28,11 @@ const { }, } = adaptersRT; -const { downloadDICOMData } = helpers; const commandsModule = ({ servicesManager, extensionManager, + commandsManager, }: Types.Extensions.ExtensionParams): Types.Extensions.CommandsModule => { const { segmentationService, displaySetService, viewportGridService } = servicesManager.services as AppTypes.Services; @@ -194,8 +193,11 @@ const commandsModule = ({ const generatedSegmentation = actions.generateSegmentation({ segmentationId, }); - - downloadDICOMData(generatedSegmentation.dataset, `${segmentationInOHIF.label}`); + const storeFn = commandsManager.runCommand('createStoreFunction', { + dataSource: 'download', + defaultFileName: `${segmentationInOHIF.label}.dcm`, + }); + storeFn(generatedSegmentation.dataset); }, /** * Stores a segmentation based on the provided segmentationId into a specified data source. @@ -217,11 +219,10 @@ const commandsModule = ({ } const { label, predecessorImageId } = segmentation; - const defaultDataSource = dataSource ?? extensionManager.getActiveDataSource()[0]; const { value: reportName, - dataSourceName: selectedDataSource, + dataSourceName, series, priorSeriesNumber, action, @@ -231,50 +232,58 @@ const commandsModule = ({ predecessorImageId, title: 'Store Segmentation', modality, + enableDownload: true, }); - if (action === PROMPT_RESPONSES.CREATE_REPORT) { - try { - const selectedDataSourceConfig = selectedDataSource - ? extensionManager.getDataSources(selectedDataSource)[0] - : defaultDataSource; + if (action !== PROMPT_RESPONSES.CREATE_REPORT) { + return; + } - const args = { - segmentationId, - options: { - SeriesDescription: series ? undefined : reportName || label || 'Contour Series', - SeriesNumber: series ? undefined : 1 + priorSeriesNumber, - predecessorImageId: series, - }, - }; - const generatedDataAsync = - (modality === 'SEG' && actions.generateSegmentation(args)) || - (modality === 'RTSTRUCT' && actions.generateContour(args)); - const generatedData = await generatedDataAsync; + const defaultFileName = + modality === 'RTSTRUCT' + ? `rtss-${segmentationId}.dcm` + : `${label || 'segmentation'}.dcm`; - if (!generatedData || !generatedData.dataset) { - throw new Error('Error during segmentation generation'); - } + const storeFn = commandsManager.runCommand('createStoreFunction', { + dataSource: dataSourceName, + defaultFileName, + }); - const { dataset: naturalizedReport } = generatedData; + if (!storeFn) { + throw new Error(`No valid store for dataSource: ${dataSourceName}`); + } - // DCMJS assigns a dummy study id during creation, and this can cause problems, so clearing it out - if (naturalizedReport.StudyID === 'No Study ID') { - naturalizedReport.StudyID = ''; - } + try { + const args = { + segmentationId, + options: { + SeriesDescription: series ? undefined : reportName || label || 'Contour Series', + SeriesNumber: series ? undefined : 1 + priorSeriesNumber, + predecessorImageId: series, + }, + }; + const generatedDataAsync = + (modality === 'SEG' && actions.generateSegmentation(args)) || + (modality === 'RTSTRUCT' && actions.generateContour(args)); + const generatedData = await generatedDataAsync; - await selectedDataSourceConfig.store.dicom(naturalizedReport); - - // add the information for where we stored it to the instance as well - naturalizedReport.wadoRoot = selectedDataSourceConfig.getConfig().wadoRoot; - - DicomMetadataStore.addInstances([naturalizedReport], true); - - return naturalizedReport; - } catch (error) { - console.debug('Error storing segmentation:', error); - throw error; + if (!generatedData?.dataset) { + throw new Error('Error during segmentation generation'); } + + const { dataset: naturalizedReport } = generatedData; + + // DCMJS assigns a dummy study id during creation, and this can cause problems, so clearing it out + if (naturalizedReport.StudyID === 'No Study ID') { + naturalizedReport.StudyID = ''; + } + + await storeFn(naturalizedReport, {}); + + return naturalizedReport; + } catch (error) { + console.debug('Error storing segmentation:', error); + throw error; } }, @@ -307,14 +316,11 @@ const commandsModule = ({ downloadRTSS: async args => { const { dataset } = await actions.generateContour(args); const { InstanceNumber: instanceNumber = 1, SeriesInstanceUID: seriesUID } = dataset; - - try { - //Create a URL for the binary. - const filename = `rtss-${seriesUID}-${instanceNumber}.dcm`; - downloadDICOMData(dataset, filename); - } catch (e) { - console.warn(e); - } + const storeFn = commandsManager.runCommand('createStoreFunction', { + dataSource: 'download', + defaultFileName: `rtss-${seriesUID}-${instanceNumber}.dcm`, + }); + await storeFn(dataset); }, toggleActiveSegmentationUtility: ({ itemId: buttonId }) => { diff --git a/extensions/cornerstone-dicom-sr/src/commandsModule.ts b/extensions/cornerstone-dicom-sr/src/commandsModule.ts index 8ba532414..7588a9fca 100644 --- a/extensions/cornerstone-dicom-sr/src/commandsModule.ts +++ b/extensions/cornerstone-dicom-sr/src/commandsModule.ts @@ -1,14 +1,11 @@ import { metaData } from '@cornerstonejs/core'; -import OHIF, { DicomMetadataStore, utils } from '@ohif/core'; -import dcmjs from 'dcmjs'; +import OHIF from '@ohif/core'; import { adaptersSR } from '@cornerstonejs/adapters'; import getFilteredCornerstoneToolState from './utils/getFilteredCornerstoneToolState'; import hydrateStructuredReport from './utils/hydrateStructuredReport'; -const { downloadBlob } = utils; - const { MeasurementReport } = adaptersSR.Cornerstone3D; const { log } = OHIF; @@ -75,24 +72,8 @@ const commandsModule = (props: withAppTypes) => { /** * * @param measurementData An array of measurements from the measurements service - * @param additionalFindingTypes toolTypes that should be stored with labels as Findings - * @param options Naturalized DICOM JSON headers to merge into the displaySet. - * as opposed to Finding Sites. * that you wish to serialize. - */ - downloadReport: ({ measurementData, additionalFindingTypes, options = {} }) => { - const srDataset = _generateReport(measurementData, additionalFindingTypes, options); - const reportBlob = dcmjs.data.datasetToBlob(srDataset); - - //Create a URL for the binary. - downloadBlob(reportBlob, { filename: 'dicom-sr.dcm' }); - }, - - /** - * - * @param measurementData An array of measurements from the measurements service - * that you wish to serialize. - * @param dataSource The dataSource that you wish to use to persist the data. + * @param dataSource The data source name ('download', 'copyToClipboard', or a named data source). * @param additionalFindingTypes toolTypes that should be stored with labels as Findings * @param options Naturalized DICOM JSON headers to merge into the displaySet. * @return The naturalized report @@ -103,23 +84,30 @@ const commandsModule = (props: withAppTypes) => { additionalFindingTypes, options = {}, }) => { - // Use the @cornerstonejs adapter for converting to/from DICOM - // But it is good enough for now whilst we only have cornerstone as a datasource. log.info('[DICOMSR] storeMeasurements'); - if (!dataSource || !dataSource.store || !dataSource.store.dicom) { - log.error('[DICOMSR] datasource has no dataSource.store.dicom endpoint!'); + const storeFn = commandsManager.runCommand('createStoreFunction', { + dataSource, + defaultFileName: 'dicom-sr.dcm', + }); + + if (!storeFn) { + log.error('[DICOMSR] No valid store for dataSource:', dataSource); return Promise.reject({}); } try { - const naturalizedReport = _generateReport(measurementData, additionalFindingTypes, options); + const naturalizedReport = _generateReport( + measurementData, + additionalFindingTypes, + options + ); - const { StudyInstanceUID, ContentSequence } = naturalizedReport; + const { ContentSequence } = naturalizedReport; // The content sequence has 5 or more elements, of which // the `[4]` element contains the annotation data, so this is // checking that there is some annotation data present. - if (!ContentSequence?.[4].ContentSequence?.length) { + if (!ContentSequence?.[4]?.ContentSequence?.length) { console.log('naturalizedReport missing imaging content', naturalizedReport); throw new Error('Invalid report, no content'); } @@ -128,22 +116,12 @@ const commandsModule = (props: withAppTypes) => { } const onBeforeDicomStore = customizationService.getCustomization('onBeforeDicomStore'); - let dicomDict; if (typeof onBeforeDicomStore === 'function') { dicomDict = onBeforeDicomStore({ dicomDict, measurementData, naturalizedReport }); } - await dataSource.store.dicom(naturalizedReport, null, dicomDict); - - if (StudyInstanceUID) { - dataSource.deleteStudyMetadataPromise(StudyInstanceUID); - } - - // The "Mode" route listens for DicomMetadataStore changes - // When a new instance is added, it listens and - // automatically calls makeDisplaySets - DicomMetadataStore.addInstances([naturalizedReport], true); + await storeFn(naturalizedReport, { measurementData, dicomDict }); return naturalizedReport; } catch (error) { @@ -166,7 +144,6 @@ const commandsModule = (props: withAppTypes) => { }; const definitions = { - downloadReport: actions.downloadReport, storeMeasurements: actions.storeMeasurements, hydrateStructuredReport: actions.hydrateStructuredReport, }; diff --git a/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.ts b/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.ts index dcec2f4b1..f30dda6cb 100644 --- a/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.ts +++ b/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.ts @@ -637,16 +637,35 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) { NUMContentItems.forEach(item => { const { ConceptNameCodeSequence, ContentSequence, MeasuredValueSequence } = item; - // Handle spatial reference ONLY if ContentSequence exists + // Handle spatial reference ONLY if ContentSequence exists. + // ContentSequence may be a scalar SCOORD or an array when additional named + // SCOORDs (e.g. control points) are nested alongside the primary geometry. + // Pick the primary geometry entry: prefer the SCOORD without a + // ConceptNameCodeSequence (plain polyline), falling back to the first SCOORD. if (ContentSequence) { - const { ValueType } = ContentSequence; + const scoordItem = Array.isArray(ContentSequence) + ? (ContentSequence.find( + cs => + (cs.ValueType === 'SCOORD' || cs.ValueType === 'SCOORD3D') && + !cs.ConceptNameCodeSequence + ) ?? ContentSequence.find(cs => cs.ValueType === 'SCOORD' || cs.ValueType === 'SCOORD3D')) + : ContentSequence; + + if (!scoordItem) { + console.warn( + 'ContentSequence array contains no SCOORD or SCOORD3D entry, skipping annotation.' + ); + return; + } + + const { ValueType } = scoordItem; if (ValueType !== 'SCOORD' && ValueType !== 'SCOORD3D') { console.warn(`Graphic ${ValueType} not currently supported, skipping annotation.`); return; } - const coords = _getCoordsFromSCOORDOrSCOORD3D(ContentSequence); + const coords = _getCoordsFromSCOORDOrSCOORD3D(scoordItem); if (coords) { measurement.coords.push(coords); diff --git a/extensions/cornerstone/src/components/ExportSegmentationSubMenuItem.tsx b/extensions/cornerstone/src/components/ExportSegmentationSubMenuItem.tsx index 10eec5e8e..56bc5ab30 100644 --- a/extensions/cornerstone/src/components/ExportSegmentationSubMenuItem.tsx +++ b/extensions/cornerstone/src/components/ExportSegmentationSubMenuItem.tsx @@ -5,9 +5,7 @@ import { DropdownMenuSubTrigger, DropdownMenuPortal, DropdownMenuSubContent, - DropdownMenuLabel, DropdownMenuItem, - DropdownMenuSeparator, Icons, } from '@ohif/ui-next'; @@ -19,8 +17,6 @@ interface ExportSegmentationSubMenuItemProps { allowExport: boolean; actions: { storeSegmentation: (segmentationId: string, modality?: string) => Promise; - onSegmentationDownloadRTSS: (segmentationId: string) => void; - onSegmentationDownload: (segmentationId: string) => void; downloadCSVSegmentationReport: (segmentationId: string) => void; }; } @@ -37,14 +33,10 @@ export const ExportSegmentationSubMenuItem: React.FC - {t('Download & Export')} + {t('Export')} - - - {t('Download')} - {segmentationRepresentationType === SegmentationRepresentations.Labelmap && ( { @@ -56,31 +48,6 @@ export const ExportSegmentationSubMenuItem: React.FC )} - {segmentationRepresentationType === SegmentationRepresentations.Labelmap && ( - { - e.preventDefault(); - actions.onSegmentationDownload(segmentationId); - }} - disabled={!allowExport} - > - {t('DICOM SEG')} - - )} - { - e.preventDefault(); - actions.onSegmentationDownloadRTSS(segmentationId); - }} - disabled={!allowExport} - > - {t('DICOM RTSS')} - - - - - {t('Export')} - {segmentationRepresentationType === SegmentationRepresentations.Labelmap && ( { diff --git a/extensions/cornerstone/src/customizations/CustomDropdownMenuContent.tsx b/extensions/cornerstone/src/customizations/CustomDropdownMenuContent.tsx index 072d25da7..f93e77085 100644 --- a/extensions/cornerstone/src/customizations/CustomDropdownMenuContent.tsx +++ b/extensions/cornerstone/src/customizations/CustomDropdownMenuContent.tsx @@ -64,12 +64,6 @@ export const CustomDropdownMenuContent = () => { context: 'CORNERSTONE', }); }, - onSegmentationDownloadRTSS: segmentationId => { - commandsManager.run('downloadRTSS', { segmentationId }); - }, - onSegmentationDownload: segmentationId => { - commandsManager.run('downloadSegmentation', { segmentationId }); - }, downloadCSVSegmentationReport: segmentationId => { commandsManager.run('downloadCSVSegmentationReport', { segmentationId }); }, diff --git a/extensions/default/src/Actions/createReportAsync.tsx b/extensions/default/src/Actions/createReportAsync.tsx index 1cffefc96..1718a7bf5 100644 --- a/extensions/default/src/Actions/createReportAsync.tsx +++ b/extensions/default/src/Actions/createReportAsync.tsx @@ -1,5 +1,3 @@ -import { DicomMetadataStore } from '@ohif/core'; - /** * * @param {*} servicesManager @@ -7,7 +5,8 @@ import { DicomMetadataStore } from '@ohif/core'; async function createReportAsync({ servicesManager, getReport, - reportType = 'measurement', + reportType = 'Measurements', + successMessage, }: withAppTypes) { const { displaySetService, uiNotificationService, uiDialogService } = servicesManager.services; @@ -18,18 +17,15 @@ async function createReportAsync({ return; } - // The "Mode" route listens for DicomMetadataStore changes - // When a new instance is added, it listens and - // automatically calls makeDisplaySets - DicomMetadataStore.addInstances([naturalizedReport], true); - + // addInstances is called by the store command (storeMeasurements/storeSegmentation), + // so the display set should already exist at this point. const displaySet = displaySetService.getMostRecentDisplaySet(); const displaySetInstanceUID = displaySet.displaySetInstanceUID; uiNotificationService.show({ title: 'Create Report', - message: `${reportType} saved successfully`, + message: successMessage ?? `${reportType} saved successfully`, type: 'success', }); diff --git a/extensions/default/src/Panels/createReportDialogPrompt.tsx b/extensions/default/src/Panels/createReportDialogPrompt.tsx index 6f39153ca..daa18febf 100644 --- a/extensions/default/src/Panels/createReportDialogPrompt.tsx +++ b/extensions/default/src/Panels/createReportDialogPrompt.tsx @@ -30,6 +30,7 @@ export default function CreateReportDialogPrompt({ predecessorImageId, extensionManager, servicesManager, + enableDownload = false, }): Promise<{ value: string; dataSourceName: string; @@ -59,6 +60,7 @@ export default function CreateReportDialogPrompt({ predecessorImageId, minSeriesNumber, modality, + enableDownload, onSave: async ({ reportName, dataSource: selectedDataSource, diff --git a/extensions/default/src/commandsModule.ts b/extensions/default/src/commandsModule.ts index 9f18269f5..6c2fb8dfa 100644 --- a/extensions/default/src/commandsModule.ts +++ b/extensions/default/src/commandsModule.ts @@ -1,4 +1,7 @@ -import { Types, DicomMetadataStore } from '@ohif/core'; +import { Types, DicomMetadataStore, utils } from '@ohif/core'; +import dcmjs from 'dcmjs'; + +const { downloadBlob } = utils; import { ContextMenuController } from './CustomizableContextMenu'; import DicomTagBrowser from './DicomTagBrowser/DicomTagBrowser'; @@ -768,6 +771,67 @@ const commandsModule = ({ setTimeout(() => actions.scrollActiveThumbnailIntoView(), 0); }, + + /** + * Creates a store function based on the data source type. + * @param dataSource - 'download', 'copyToClipboard', or a named data source + * @param defaultFileName - Default filename for download/clipboard + * @param defaultContentType - Default content type for clipboard + * @returns A store function, or null if no valid store exists + */ + createStoreFunction: ({ dataSource, defaultFileName, defaultContentType }) => { + if (dataSource === 'download') { + return async dicom => { + const instances = Array.isArray(dicom) ? dicom : [dicom]; + DicomMetadataStore.addInstances(instances, true); + if (instances.length !== 1) { + throw new Error('Download only supports a single DICOM instance'); + } + const reportBlob = dcmjs.data.datasetToBlob(instances[0]); + downloadBlob(reportBlob, { filename: defaultFileName || 'dicom.dcm' }); + }; + } + + if (dataSource === 'copyToClipboard') { + return async dicom => { + const instances = Array.isArray(dicom) ? dicom : [dicom]; + DicomMetadataStore.addInstances(instances, true); + if (instances.length !== 1) { + throw new Error('Copy to clipboard only supports a single DICOM instance'); + } + const reportBlob = dcmjs.data.datasetToBlob(instances[0]); + const type = defaultContentType || 'application/dicom'; + await navigator.clipboard.write([ + new ClipboardItem({ [type]: new Blob([reportBlob], { type }) }), + ]); + }; + } + + // DICOM STOW path — resolve the named data source + const dataSources = extensionManager.getDataSources(dataSource); + const resolvedDataSource = dataSources?.[0]; + if (!resolvedDataSource?.store?.dicom) { + return null; + } + + return async (dicom, { dicomDict } = {}) => { + const instances = Array.isArray(dicom) ? dicom : [dicom]; + const config = resolvedDataSource.getConfig?.(); + if (config?.wadoRoot) { + instances.forEach(instance => { + instance.wadoRoot = config.wadoRoot; + }); + } + DicomMetadataStore.addInstances(instances, true); + for (const instance of instances) { + await resolvedDataSource.store.dicom(instance, null, dicomDict); + } + const studyUIDs = new Set(instances.map(i => i.StudyInstanceUID).filter(Boolean)); + for (const uid of studyUIDs) { + resolvedDataSource.deleteStudyMetadataPromise(uid); + } + }; + }, }; const definitions = { @@ -796,6 +860,7 @@ const commandsModule = ({ scrollActiveThumbnailIntoView: actions.scrollActiveThumbnailIntoView, addDisplaySetAsLayer: actions.addDisplaySetAsLayer, removeDisplaySetLayer: actions.removeDisplaySetLayer, + createStoreFunction: actions.createStoreFunction, }; return { diff --git a/extensions/default/src/customizations/reportDialogCustomization.tsx b/extensions/default/src/customizations/reportDialogCustomization.tsx index 36c25f787..3ac79db98 100644 --- a/extensions/default/src/customizations/reportDialogCustomization.tsx +++ b/extensions/default/src/customizations/reportDialogCustomization.tsx @@ -1,4 +1,5 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { InputDialog } from '@ohif/ui-next'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@ohif/ui-next'; import { useSystem } from '@ohif/core'; @@ -21,6 +22,7 @@ type ReportDialogProps = { priorSeriesNumber: number; }) => void; onCancel: () => void; + enableDownload?: boolean; }; function ReportDialog({ @@ -31,8 +33,11 @@ function ReportDialog({ hide, onSave, onCancel, + enableDownload = false, }: ReportDialogProps) { + const { t } = useTranslation('Buttons'); const { servicesManager } = useSystem(); + const actionTakenRef = useRef(false); const [selectedDataSource, setSelectedDataSource] = useState( dataSources?.[0]?.value ?? null ); @@ -72,6 +77,7 @@ function ReportDialog({ }, [selectedSeries, seriesOptions]); const handleSave = useCallback(() => { + actionTakenRef.current = true; onSave({ reportName, dataSource: selectedDataSource, @@ -82,11 +88,33 @@ function ReportDialog({ }, [selectedDataSource, selectedSeries, reportName, hide, onSave]); const handleCancel = useCallback(() => { + actionTakenRef.current = true; onCancel(); hide(); }, [onCancel, hide]); + const handleDownload = useCallback(() => { + actionTakenRef.current = true; + onSave({ + reportName, + dataSource: 'download', + priorSeriesNumber: Math.max(...seriesOptions.map(it => it.seriesNumber)), + series: selectedSeries, + }); + hide(); + }, [selectedDataSource, selectedSeries, reportName, hide, onSave]); + + // Handles the close dialog button/external close as a cancel + useEffect(() => { + return () => { + if (!actionTakenRef.current) { + onCancel(); + } + }; + }, [onCancel]); + const showDataSourceSelect = dataSources?.length > 1; + const showDownloadButton = enableDownload; return (
@@ -181,9 +209,11 @@ function ReportDialog({
- - Cancel - + {showDownloadButton && ( + + {t('Download')} + + )} Save diff --git a/extensions/default/src/utils/promptSaveReport.tsx b/extensions/default/src/utils/promptSaveReport.tsx index ac219bf6a..59cb5e8bc 100644 --- a/extensions/default/src/utils/promptSaveReport.tsx +++ b/extensions/default/src/utils/promptSaveReport.tsx @@ -35,21 +35,19 @@ async function promptSaveReport({ servicesManager, commandsManager, extensionMan minSeriesNumber: 3000, extensionManager, servicesManager, + enableDownload: true, }); if (promptResult.action === PROMPT_RESPONSES.CREATE_REPORT) { - const dataSources = extensionManager.getDataSources(promptResult.dataSourceName); - const dataSource = dataSources[0]; - - const { series, priorSeriesNumber, value: reportName } = promptResult; + const { series, priorSeriesNumber, value: reportName, dataSourceName } = promptResult; const SeriesDescription = reportName || defaultSaveTitle; - const getReport = async () => { - return commandsManager.runCommand( + const getReport = async () => + commandsManager.runCommand( 'storeMeasurements', { measurementData, - dataSource, + dataSource: dataSourceName, additionalFindingTypes: ['ArrowAnnotate'], options: { SeriesDescription, @@ -59,12 +57,12 @@ async function promptSaveReport({ servicesManager, commandsManager, extensionMan }, 'CORNERSTONE_STRUCTURED_REPORT' ); - }; + displaySetInstanceUIDs = await createReportAsync({ servicesManager, getReport, }); - } else if (promptResult.action === RESPONSE.CANCEL) { + } else if (promptResult.action === PROMPT_RESPONSES.CANCEL) { // Do nothing } diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/TrackedMeasurementsContext.tsx b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/TrackedMeasurementsContext.tsx index 31e715792..9d6f70273 100644 --- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/TrackedMeasurementsContext.tsx +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/TrackedMeasurementsContext.tsx @@ -137,8 +137,9 @@ function TrackedMeasurementsContextProvider( }); }, showStructuredReportDisplaySetInActiveViewport: (ctx, evt) => { - if (evt.data.createdDisplaySetInstanceUIDs.length > 0) { - const StructuredReportDisplaySetInstanceUID = evt.data.createdDisplaySetInstanceUIDs[0]; + const uids = evt.data?.createdDisplaySetInstanceUIDs; + if (uids?.length > 0) { + const StructuredReportDisplaySetInstanceUID = uids[0]; viewportGridService.setDisplaySetsForViewport({ viewportId: evt.data.viewportId, diff --git a/testdata b/testdata index 3f85fe843..bd743ed76 160000 --- a/testdata +++ b/testdata @@ -1 +1 @@ -Subproject commit 3f85fe843c9a1ffaccb826845f317dace9c06c45 +Subproject commit bd743ed76b403e6d476e1af0bbfe7479d96b563d