From 5c450cbd7b5f01c48e674f16459abaf3c052585a Mon Sep 17 00:00:00 2001 From: Deb Date: Fri, 26 Jun 2026 13:45:38 -0500 Subject: [PATCH 1/2] fix(segmentation): clarify save destination dialog --- .../src/panels/PanelSegmentation.tsx | 41 +++ .../reportDialogCustomization.tsx | 268 ++++++++++-------- 2 files changed, 193 insertions(+), 116 deletions(-) diff --git a/extensions/cornerstone/src/panels/PanelSegmentation.tsx b/extensions/cornerstone/src/panels/PanelSegmentation.tsx index c43a08cb0a2..71c3cb4c278 100644 --- a/extensions/cornerstone/src/panels/PanelSegmentation.tsx +++ b/extensions/cornerstone/src/panels/PanelSegmentation.tsx @@ -1,5 +1,6 @@ import React, { useCallback, useEffect } from 'react'; import { + Icons, IconPresentationProvider, Popover, PopoverAnchor, @@ -232,6 +233,18 @@ export default function PanelSegmentation({ }; }); + const saveModality = + segmentationRepresentationTypes?.[0] === SegmentationRepresentations.Contour + ? 'RTSTRUCT' + : 'SEG'; + + const isCurrentSegExportable = !!( + selectedSegmentationIdForType && + exportOptions.some( + o => o.segmentationId === selectedSegmentationIdForType && o.isExportable + ) + ); + // Common props for SegmentationTable const tableProps = { disabled, @@ -276,6 +289,32 @@ export default function PanelSegmentation({ ); }; + const renderSaveButton = () => { + if (!isCurrentSegExportable) { + return null; + } + return ( + + ); + }; + // Render content based on mode const renderModeContent = () => { if (tableProps.mode === 'collapsed') { @@ -288,6 +327,7 @@ export default function PanelSegmentation({ + {renderSaveButton()} @@ -307,6 +347,7 @@ export default function PanelSegmentation({ + {renderSaveButton()} diff --git a/extensions/default/src/customizations/reportDialogCustomization.tsx b/extensions/default/src/customizations/reportDialogCustomization.tsx index 3ac79db9861..2147b32b264 100644 --- a/extensions/default/src/customizations/reportDialogCustomization.tsx +++ b/extensions/default/src/customizations/reportDialogCustomization.tsx @@ -14,6 +14,7 @@ type ReportDialogProps = { dataSources: DataSource[]; modality?: string; predecessorImageId?: string; + minSeriesNumber?: number; hide: () => void; onSave: (data: { reportName: string; @@ -25,6 +26,8 @@ type ReportDialogProps = { enableDownload?: boolean; }; +type SaveMode = 'existing' | 'new'; + function ReportDialog({ dataSources, modality = 'SR', @@ -43,49 +46,65 @@ function ReportDialog({ ); const { displaySetService } = servicesManager.services; - const [selectedSeries, setSelectedSeries] = useState(predecessorImageId || null); - const [reportName, setReportName] = useState(''); - - const seriesOptions = useMemo(() => { + const existingSeriesOptions = useMemo(() => { const displaySetsMap = displaySetService.getDisplaySetCache(); const displaySets = Array.from(displaySetsMap.values()); - const options = displaySets + return displaySets .filter(ds => ds.Modality === modality) .map(ds => ({ value: ds.predecessorImageId || ds.SeriesInstanceUID, seriesNumber: isFinite(ds.SeriesNumber) ? ds.SeriesNumber : minSeriesNumber, - description: ds.SeriesDescription, + description: ds.SeriesDescription ?? '', label: `${ds.SeriesDescription} ${ds.SeriesDate}/${ds.SeriesTime} ${ds.SeriesNumber}`, })); + }, [displaySetService, modality, minSeriesNumber]); - return [ - { - value: null, - description: null, - seriesNumber: minSeriesNumber, - label: 'Create new series', - }, - ...options, - ]; - }, [displaySetService, modality]); + const hasExistingSeries = existingSeriesOptions.length > 0; - useEffect(() => { - const seriesOption = seriesOptions.find(s => s.value === selectedSeries); - const newReportName = - selectedSeries && seriesOption?.description ? seriesOption.description : ''; - setReportName(newReportName); - }, [selectedSeries, seriesOptions]); + const [saveMode, setSaveMode] = useState(hasExistingSeries ? 'existing' : 'new'); + const [selectedSeries, setSelectedSeries] = useState( + predecessorImageId + ? (existingSeriesOptions.find(s => s.value === predecessorImageId)?.value ?? + existingSeriesOptions[0]?.value ?? + null) + : (existingSeriesOptions[0]?.value ?? null) + ); + const [newSeriesName, setNewSeriesName] = useState(''); + + const priorSeriesNumber = useMemo( + () => Math.max(minSeriesNumber, ...existingSeriesOptions.map(s => s.seriesNumber)), + [existingSeriesOptions, minSeriesNumber] + ); + + const existingSeriesDesc = useMemo( + () => existingSeriesOptions.find(s => s.value === selectedSeries)?.description ?? '', + [existingSeriesOptions, selectedSeries] + ); + + const switchToNew = useCallback(() => { + setSaveMode('new'); + setNewSeriesName(prev => prev || (existingSeriesDesc ? `${existingSeriesDesc} copy` : '')); + }, [existingSeriesDesc]); const handleSave = useCallback(() => { actionTakenRef.current = true; onSave({ - reportName, + reportName: saveMode === 'existing' ? existingSeriesDesc : newSeriesName, dataSource: selectedDataSource, - priorSeriesNumber: Math.max(...seriesOptions.map(it => it.seriesNumber)), - series: selectedSeries, + priorSeriesNumber, + series: saveMode === 'existing' ? selectedSeries : null, }); hide(); - }, [selectedDataSource, selectedSeries, reportName, hide, onSave]); + }, [ + saveMode, + existingSeriesDesc, + newSeriesName, + selectedDataSource, + priorSeriesNumber, + selectedSeries, + hide, + onSave, + ]); const handleCancel = useCallback(() => { actionTakenRef.current = true; @@ -96,13 +115,21 @@ function ReportDialog({ const handleDownload = useCallback(() => { actionTakenRef.current = true; onSave({ - reportName, + reportName: saveMode === 'existing' ? existingSeriesDesc : newSeriesName, dataSource: 'download', - priorSeriesNumber: Math.max(...seriesOptions.map(it => it.seriesNumber)), - series: selectedSeries, + priorSeriesNumber, + series: saveMode === 'existing' ? selectedSeries : null, }); hide(); - }, [selectedDataSource, selectedSeries, reportName, hide, onSave]); + }, [ + saveMode, + existingSeriesDesc, + newSeriesName, + priorSeriesNumber, + selectedSeries, + hide, + onSave, + ]); // Handles the close dialog button/external close as a cancel useEffect(() => { @@ -114,102 +141,111 @@ function ReportDialog({ }, [onCancel]); const showDataSourceSelect = dataSources?.length > 1; - const showDownloadButton = enableDownload; return (
-
- {showDataSourceSelect && ( - <> -
-
Data source
- -
-
-
Series
- -
- - )} + {showDataSourceSelect && ( +
+
Data source
+ +
+ )} + + {/* Save to existing series */} +
+ +
+ +
-
- {!showDataSourceSelect && ( -
-
Series
- -
- )} - - - - - + + {/* Create new series — entire section clickable to switch mode */} +
+ +
+ + + + + + {saveMode === 'existing' && ( +

+ Series name cannot be changed here +

+ )} +
- {showDownloadButton && ( + {enableDownload && ( {t('Download')} From 4651c35be917d2c1ba3b0ee2f8554673dde8d04c Mon Sep 17 00:00:00 2001 From: Deb Date: Mon, 29 Jun 2026 14:02:16 -0500 Subject: [PATCH 2/2] fix(segmentation): use save icon for segmentation action Replace the database cylinder with a file save icon so the panel action reads as saving a segmentation rather than selecting storage. Register the icon in ui-next for reuse. Co-authored-by: Cursor --- .../src/panels/PanelSegmentation.tsx | 2 +- .../ui-next/src/components/Icons/Icons.tsx | 2 ++ .../src/components/Icons/Sources/Save.tsx | 26 +++++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 platform/ui-next/src/components/Icons/Sources/Save.tsx diff --git a/extensions/cornerstone/src/panels/PanelSegmentation.tsx b/extensions/cornerstone/src/panels/PanelSegmentation.tsx index 71c3cb4c278..11b1c64cc1f 100644 --- a/extensions/cornerstone/src/panels/PanelSegmentation.tsx +++ b/extensions/cornerstone/src/panels/PanelSegmentation.tsx @@ -310,7 +310,7 @@ export default function PanelSegmentation({ }); }} > - + ); }; diff --git a/platform/ui-next/src/components/Icons/Icons.tsx b/platform/ui-next/src/components/Icons/Icons.tsx index eccbf221696..1134d3f4c49 100644 --- a/platform/ui-next/src/components/Icons/Icons.tsx +++ b/platform/ui-next/src/components/Icons/Icons.tsx @@ -44,6 +44,7 @@ import PowerOff from './Sources/PowerOff'; import Redo from './Sources/Redo'; import Refresh from './Sources/Refresh'; import Rename from './Sources/Rename'; +import Save from './Sources/Save'; import Series from './Sources/Series'; import SeriesPlaceholder from './Sources/SeriesPlaceholder'; import Settings from './Sources/Settings'; @@ -435,6 +436,7 @@ export const Icons = { ActionNewDialog, GroupLayers, Database, + Save, InvestigationalUse, Tool3DRotate, ToolAngle, diff --git a/platform/ui-next/src/components/Icons/Sources/Save.tsx b/platform/ui-next/src/components/Icons/Sources/Save.tsx new file mode 100644 index 00000000000..008ae71cdcb --- /dev/null +++ b/platform/ui-next/src/components/Icons/Sources/Save.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import type { IconProps } from '../types'; + +export const Save = (props: IconProps) => ( + + + + + + + +); + +export default Save;