From 4b63d485188f6f4dab27f899f67f4dfd0b737f24 Mon Sep 17 00:00:00 2001 From: Yiannis Theocharakis <35450092+YiannisTheocharakis@users.noreply.github.com> Date: Tue, 25 Feb 2025 23:25:52 +0200 Subject: [PATCH] feat(Segments Looping) : Fixed looping through Segments of SEG (#4793) --- .../viewports/OHIFCornerstoneRTViewport.tsx | 42 ++++------------- .../viewports/OHIFCornerstoneSEGViewport.tsx | 31 ++++-------- extensions/cornerstone/src/index.tsx | 2 + extensions/cornerstone/src/utils/index.ts | 7 +++ .../cornerstone/src/utils/segmentUtils.ts | 47 +++++++++++++++++++ .../src/components/DataRow/DataRow.tsx | 13 ++++- 6 files changed, 87 insertions(+), 55 deletions(-) create mode 100644 extensions/cornerstone/src/utils/index.ts create mode 100644 extensions/cornerstone/src/utils/segmentUtils.ts diff --git a/extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx b/extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx index ba64c08ce..6dc0b4af4 100644 --- a/extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx +++ b/extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx @@ -2,6 +2,7 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'; import PropTypes from 'prop-types'; import { ViewportActionArrows } from '@ohif/ui'; import { useViewportGrid } from '@ohif/ui-next'; +import { utils } from '@ohif/extension-cornerstone'; import promptHydrateRT from '../utils/promptHydrateRT'; import _getStatusComponent from './_getStatusComponent'; @@ -48,7 +49,7 @@ function OHIFCornerstoneRTViewport(props: withAppTypes) { const [viewportGrid, viewportGridService] = useViewportGrid(); // States - let selectedSegmentObjectIndex: number = 0; + const selectedSegmentObjectIndex: number = 0; const { setPositionPresentation } = usePositionPresentationStore(); // Hydration means that the RT is opened and segments are loaded into the @@ -141,38 +142,15 @@ function OHIFCornerstoneRTViewport(props: withAppTypes) { const onSegmentChange = useCallback( direction => { - const segmentationId = rtDisplaySet.displaySetInstanceUID; - const segmentation = segmentationService.getSegmentation(segmentationId); - - const { segments } = segmentation; - - const numberOfSegments = Object.keys(segments).length; - //Get activeSegment each time because the user can select any segment from the list and thus the index should be updated - const activeSegment = segmentationService.getActiveSegment(viewportId); - if (activeSegment) { - const activeSegmentIndex = Object.values(segments).findIndex( - segment => segment.segmentIndex === activeSegment.segmentIndex - ); - //from the activeSegment get the actual obeject array index to be used - selectedSegmentObjectIndex = activeSegmentIndex; - } - let newSelectedSegmentIndex = selectedSegmentObjectIndex + direction; - - //Handle looping through list of segments - if (newSelectedSegmentIndex > numberOfSegments - 1) { - newSelectedSegmentIndex = 0; - } else if (newSelectedSegmentIndex < 0) { - newSelectedSegmentIndex = numberOfSegments - 1; - } - - //convert segmentationId from object array index to property value of type Segment - //Functions below uses the segmentIndex object attribute so we have to do the conversion - const keyIndex = Object.values(segments)[newSelectedSegmentIndex]?.segmentIndex; - segmentationService.setActiveSegment(segmentationId, keyIndex); - segmentationService.jumpToSegmentCenter(segmentationId, keyIndex, viewportId); - selectedSegmentObjectIndex = newSelectedSegmentIndex; + utils.handleSegmentChange({ + direction, + segDisplaySet: rtDisplaySet, + viewportId, + selectedSegmentObjectIndex, + segmentationService, + }); }, - [selectedSegmentObjectIndex, segmentationService] + [selectedSegmentObjectIndex] ); useEffect(() => { diff --git a/extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx b/extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx index 311ada438..2cf4d0c0c 100644 --- a/extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx +++ b/extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx @@ -7,6 +7,7 @@ import promptHydrateSEG from '../utils/promptHydrateSEG'; import _getStatusComponent from './_getStatusComponent'; import { usePositionPresentationStore } from '@ohif/extension-cornerstone'; import { SegmentationRepresentations } from '@cornerstonejs/tools/enums'; +import { utils } from '@ohif/extension-cornerstone'; const SEG_TOOLGROUP_BASE_NAME = 'SEGToolGroup'; @@ -46,7 +47,7 @@ function OHIFCornerstoneSEGViewport(props: withAppTypes) { const [viewportGrid, viewportGridService] = useViewportGrid(); // States - const [selectedSegment, setSelectedSegment] = useState(1); + let selectedSegmentObjectIndex: number = 0; const { setPositionPresentation } = usePositionPresentationStore(); // Hydration means that the SEG is opened and segments are loaded into the @@ -131,27 +132,15 @@ function OHIFCornerstoneSEGViewport(props: withAppTypes) { const onSegmentChange = useCallback( direction => { - const segmentationId = segDisplaySet.displaySetInstanceUID; - const segmentation = segmentationService.getSegmentation(segmentationId); - - const { segments } = segmentation; - - const numberOfSegments = Object.keys(segments).length; - - let newSelectedSegmentIndex = selectedSegment + direction; - - // Segment 0 is always background - - if (newSelectedSegmentIndex > numberOfSegments - 1) { - newSelectedSegmentIndex = 1; - } else if (newSelectedSegmentIndex === 0) { - newSelectedSegmentIndex = numberOfSegments - 1; - } - - segmentationService.jumpToSegmentCenter(segmentationId, newSelectedSegmentIndex, viewportId); - setSelectedSegment(newSelectedSegmentIndex); + utils.handleSegmentChange({ + direction, + segDisplaySet: segDisplaySet, + viewportId, + selectedSegmentObjectIndex, + segmentationService, + }); }, - [selectedSegment] + [selectedSegmentObjectIndex] ); const hydrateSEG = useCallback(() => { diff --git a/extensions/cornerstone/src/index.tsx b/extensions/cornerstone/src/index.tsx index de7c22116..3af9177d1 100644 --- a/extensions/cornerstone/src/index.tsx +++ b/extensions/cornerstone/src/index.tsx @@ -54,6 +54,7 @@ import PanelMeasurement from './panels/PanelMeasurement'; import DicomUpload from './components/DicomUpload/DicomUpload'; import { useSegmentations } from './hooks/useSegmentations'; import { StudySummaryFromMetadata } from './components/StudySummaryFromMetadata'; +import utils from './utils'; const { imageRetrieveMetadataProvider } = cornerstone.utilities; @@ -254,5 +255,6 @@ export { PanelMeasurement, DicomUpload, StudySummaryFromMetadata, + utils, }; export default cornerstoneExtension; diff --git a/extensions/cornerstone/src/utils/index.ts b/extensions/cornerstone/src/utils/index.ts new file mode 100644 index 000000000..4df1f248b --- /dev/null +++ b/extensions/cornerstone/src/utils/index.ts @@ -0,0 +1,7 @@ +import { handleSegmentChange } from './segmentUtils'; + +const utils = { + handleSegmentChange, +}; + +export default utils; diff --git a/extensions/cornerstone/src/utils/segmentUtils.ts b/extensions/cornerstone/src/utils/segmentUtils.ts new file mode 100644 index 000000000..dd43734f1 --- /dev/null +++ b/extensions/cornerstone/src/utils/segmentUtils.ts @@ -0,0 +1,47 @@ +import SegmentationServiceType from '../services/SegmentationService'; + +export const handleSegmentChange = ({ + direction, + segDisplaySet, + viewportId, + selectedSegmentObjectIndex, + segmentationService, +}: { + direction: number; + segDisplaySet: AppTypes.DisplaySet; + viewportId: string; + selectedSegmentObjectIndex: number; + segmentationService: SegmentationServiceType; +}) => { + const segmentationId = segDisplaySet.displaySetInstanceUID; + const segmentation = segmentationService.getSegmentation(segmentationId); + + const { segments } = segmentation; + + const numberOfSegments = Object.keys(segments).length; + + // Get activeSegment each time because the user can select any segment from the list and thus the index should be updated + const activeSegment = segmentationService.getActiveSegment(viewportId); + if (activeSegment) { + // from the activeSegment get the actual object array index to be used + selectedSegmentObjectIndex = Object.values(segments).findIndex( + segment => segment.segmentIndex === activeSegment.segmentIndex + ); + } + let newSelectedSegmentIndex = selectedSegmentObjectIndex + direction; + + // Handle looping through list of segments + if (newSelectedSegmentIndex > numberOfSegments - 1) { + newSelectedSegmentIndex = 0; + } else if (newSelectedSegmentIndex < 0) { + newSelectedSegmentIndex = numberOfSegments - 1; + } + + // Convert segmentationId from object array index to property value of type Segment + // Functions below use the segmentIndex object attribute so we have to do the conversion + const segmentIndex = Object.values(segments)[newSelectedSegmentIndex]?.segmentIndex; + + segmentationService.setActiveSegment(segmentationId, segmentIndex); + segmentationService.jumpToSegmentCenter(segmentationId, segmentIndex, viewportId); + selectedSegmentObjectIndex = newSelectedSegmentIndex; +}; diff --git a/platform/ui-next/src/components/DataRow/DataRow.tsx b/platform/ui-next/src/components/DataRow/DataRow.tsx index 9daf18689..b0c3e9307 100644 --- a/platform/ui-next/src/components/DataRow/DataRow.tsx +++ b/platform/ui-next/src/components/DataRow/DataRow.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import { Button } from '../../components/Button/Button'; import { DropdownMenu, @@ -97,6 +97,13 @@ const DataRow: React.FC = ({ }) => { const [isDropdownOpen, setIsDropdownOpen] = useState(false); const isTitleLong = title?.length > 25; + const rowRef = useRef(null); + + useEffect(() => { + if (isSelected && rowRef.current) { + rowRef.current.scrollIntoView({ behavior: 'smooth', block: 'center' }); + } + }, [isSelected]); const handleAction = (action: string, e: React.MouseEvent) => { e.stopPropagation(); @@ -181,7 +188,9 @@ const DataRow: React.FC = ({ }; return ( -
+