feat(data overlay): add foreground background control plus orientation change in the UI and add RTDOSE support (#5007)

This commit is contained in:
Alireza authored and GitHub committed 2025-05-05 16:35:05 -04:00
1 parent c1fced0573
commit cd4e6408bc
105 files changed
+3117 -788

No files matched your search

@@ -0,0 +1,5 @@
export { default as useViewportActionCorners } from './useViewportActionCorners';
export * from './useViewportActionCorners';
export * from './useActiveViewportSegmentationRepresentations';
export * from './useMeasurements';
export * from './useSegmentations';
@@ -84,7 +84,7 @@ function mapSegmentationToDisplay(segmentation, customizationService) {
* @param options.debounceTime - Debounce time in milliseconds for updates.
* @returns An array of segmentation data.
*/
export function useSegmentations(options: {
export function useSegmentations(options?: {
subscribeToDataModified?: boolean;
debounceTime?: number;
}): SegmentationData[] {
@@ -0,0 +1,113 @@
import { useEffect } from 'react';
import React from 'react';
import { ViewportActionCornersLocations } from '@ohif/ui-next';
import { useSystem } from '@ohif/core/src';
/**
* Viewport action corner location type
*/
export interface ViewportActionCornerLocation {
type: 'VIEWPORT';
id: string;
corner?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
}
/**
* Viewport action corner service interface
*/
export interface ViewportActionCornerService {
addComponent: (params: {
viewportId: string;
id: string;
component: React.ReactNode;
location: ViewportActionCornersLocations | string;
indexPriority?: number;
}) => void;
clear: (viewportId: string) => void;
getAlignAndSide: (location: ViewportActionCornersLocations | string) => {
align: 'start' | 'center' | 'end';
side: 'top' | 'right' | 'bottom' | 'left';
};
}
/**
* Customization service interface
*/
export interface CustomizationService {
getCustomization: (name: string) => any;
}
/**
* Props for the useViewportActionCorners hook
*/
interface UseViewportActionCornersProps {
viewportId: string;
elementRef: React.MutableRefObject<HTMLDivElement>;
displaySets: AppTypes.DisplaySet[];
}
// Map of customization keys to their corresponding enum values
const locationMap = {
'viewportActionMenu.topLeft': ViewportActionCornersLocations.topLeft,
'viewportActionMenu.topRight': ViewportActionCornersLocations.topRight,
'viewportActionMenu.bottomLeft': ViewportActionCornersLocations.bottomLeft,
'viewportActionMenu.bottomRight': ViewportActionCornersLocations.bottomRight,
};
/**
* Hook to manage viewport action corners for a Cornerstone viewport
*/
export function useViewportActionCorners({
viewportId,
elementRef,
displaySets,
}: UseViewportActionCornersProps): void {
const { servicesManager } = useSystem();
const { viewportActionCornersService, customizationService } = servicesManager.services;
useEffect(() => {
// Process each location
Object.entries(locationMap).forEach(([locationKey, locationValue]) => {
const items = customizationService.getCustomization(locationKey);
if (!items || !items.length) {
return;
}
items.forEach(item => {
if (!item.enabled) {
return;
}
const componentId = item.id;
if (item.component) {
// Use the component renderer provided directly in the item
const component = item.component({
viewportId,
element: elementRef.current,
displaySets,
location: locationValue,
});
viewportActionCornersService.addComponent({
viewportId,
id: componentId,
component,
location: locationValue,
});
} else if (item.component) {
// Handle static components
viewportActionCornersService.addComponent({
viewportId,
id: item.id,
component: item.component,
location: locationValue,
});
}
});
});
}, [displaySets, viewportId, viewportActionCornersService, elementRef]);
}
export default useViewportActionCorners;
@@ -0,0 +1,247 @@
import { useMemo } from 'react';
import { useSystem, utils } from '@ohif/core';
import { useViewportGrid } from '@ohif/ui-next';
import {
getEnhancedDisplaySets,
sortByOverlayable,
DERIVED_OVERLAY_MODALITIES,
} from '../components/ViewportDataOverlaySettingMenu/utils';
const sortByPriority = (a, b) => {
if (utils.isLowPriorityModality(a.Modality)) {
return 1;
}
return -1;
};
/**
* Options for the useViewportDisplaySets hook
*/
export type UseViewportDisplaySetsOptions = {
/**
* Whether to include background display set
*/
includeBackground?: boolean;
/**
* Whether to include foreground display sets
*/
includeForeground?: boolean;
/**
* Whether to include overlay display sets
*/
includeOverlay?: boolean;
/**
* Whether to include potential overlay display sets
*/
includePotentialOverlay?: boolean;
/**
* Whether to include potential foreground display sets
*/
includePotentialForeground?: boolean;
/**
* Whether to include potential background display sets
*/
includePotentialBackground?: boolean;
};
/**
* Return type for useViewportDisplaySets
*/
export type ViewportDisplaySets = {
/**
* The primary display set for the viewport (base image)
*/
backgroundDisplaySet?: any;
/**
* Display sets currently shown with background (non-overlay layers)
*/
foregroundDisplaySets?: any[];
/**
* Segmentation display sets currently applied as overlays
*/
overlayDisplaySets?: any[];
/**
* Display sets that could be toggled on as overlays (derived modalities)
*/
potentialOverlayDisplaySets?: any[];
/**
* Display sets that could be added as foreground layers
*/
potentialForegroundDisplaySets?: any[];
/**
* Display sets that could replace the current background
*/
potentialBackgroundDisplaySets?: any[];
};
/**
* Hook to provide display sets and overlay information for a viewport based on options.
*
* @param viewportId - The viewport ID to get display sets for
* @param options - Options to control which display sets to compute
* @returns Object containing requested display set collections based on options
*/
export function useViewportDisplaySets(
viewportId: string,
options?: UseViewportDisplaySetsOptions
): ViewportDisplaySets {
const { servicesManager } = useSystem();
const { displaySetService, segmentationService } = servicesManager.services;
// Note: this is very important we should use the useViewportGrid hook here,
// since if the viewport displaySet is changed we should re-run this hook
// to get the latest displaySets
const [_, viewportGridService] = useViewportGrid();
// Apply defaults - include everything if no options specified
const {
includeBackground = true,
includeForeground = true,
includeOverlay = true,
includePotentialOverlay = true,
includePotentialForeground = true,
includePotentialBackground = true,
} = options || {};
// Get all available display sets (only if needed)
const needsAllDisplaySets = includePotentialBackground;
const allDisplaySets = useMemo(
() => (needsAllDisplaySets ? displaySetService.getActiveDisplaySets() : []),
[displaySetService, needsAllDisplaySets]
);
// Get all available segmentations (only if needed)
const needsSegmentations = includeOverlay;
const segmentationRepresentations = useMemo(
() =>
needsSegmentations ? segmentationService.getSegmentationRepresentations(viewportId) : [],
[segmentationService, viewportId, needsSegmentations]
);
const overlayDisplaySets = useMemo(() => {
if (!includeOverlay) {
return [];
}
return segmentationRepresentations.map(repr => {
const displaySet = displaySetService.getDisplaySetByUID(repr.segmentationId);
return displaySet;
});
}, [includeOverlay, segmentationRepresentations, displaySetService]);
const overlayDisplaySetUIDs = useMemo(
() => overlayDisplaySets.map(ds => ds.displaySetInstanceUID),
[overlayDisplaySets]
);
// Get enhanced display sets (only if needed)
const needsEnhancedDisplaySets =
includeBackground || includeForeground || includePotentialOverlay || includePotentialForeground;
const { viewportDisplaySets, enhancedDisplaySets } = useMemo(() => {
if (!needsEnhancedDisplaySets) {
return { viewportDisplaySets: [], enhancedDisplaySets: [] };
}
return getEnhancedDisplaySets({
viewportId,
services: { displaySetService, viewportGridService },
});
}, [viewportId, displaySetService, viewportGridService, needsEnhancedDisplaySets]);
const backgroundDisplaySet = useMemo(
() => (includeBackground ? viewportDisplaySets[0] : undefined),
[includeBackground, viewportDisplaySets]
);
const foregroundDisplaySets = useMemo(() => {
if (!includeForeground || !backgroundDisplaySet) {
return [];
}
return viewportDisplaySets.filter(
ds =>
!DERIVED_OVERLAY_MODALITIES.includes(ds.Modality) &&
ds.displaySetInstanceUID !== backgroundDisplaySet.displaySetInstanceUID
);
}, [includeForeground, viewportDisplaySets, backgroundDisplaySet]);
const foregroundDisplaySetUIDs = useMemo(
() => foregroundDisplaySets.map(ds => ds.displaySetInstanceUID),
[foregroundDisplaySets]
);
const potentialOverlayDisplaySets = useMemo(() => {
if (!includePotentialOverlay) {
return [];
}
return enhancedDisplaySets
.filter(
ds =>
DERIVED_OVERLAY_MODALITIES.includes(ds.Modality) &&
!overlayDisplaySetUIDs.includes(ds.displaySetInstanceUID) &&
ds.isOverlayable
)
.sort(sortByOverlayable);
}, [includePotentialOverlay, enhancedDisplaySets, overlayDisplaySetUIDs]);
const potentialForegroundDisplaySets = useMemo(() => {
if (!includePotentialForeground) {
return [];
}
return enhancedDisplaySets
.filter(
ds =>
!DERIVED_OVERLAY_MODALITIES.includes(ds.Modality) &&
!foregroundDisplaySetUIDs.includes(ds.displaySetInstanceUID) &&
ds.isOverlayable
)
.sort(sortByPriority);
}, [includePotentialForeground, enhancedDisplaySets, foregroundDisplaySetUIDs]);
const potentialBackgroundDisplaySets = useMemo(() => {
if (!includePotentialBackground || !backgroundDisplaySet) {
return [];
}
return allDisplaySets
.filter(
ds =>
!DERIVED_OVERLAY_MODALITIES.includes(ds.Modality) &&
ds.displaySetInstanceUID !== backgroundDisplaySet.displaySetInstanceUID &&
!overlayDisplaySetUIDs.includes(ds.displaySetInstanceUID) &&
!foregroundDisplaySetUIDs.includes(ds.displaySetInstanceUID)
)
.sort(sortByPriority);
}, [
includePotentialBackground,
allDisplaySets,
backgroundDisplaySet,
overlayDisplaySetUIDs,
foregroundDisplaySetUIDs,
]);
const result: ViewportDisplaySets = {};
if (includeBackground) {
result.backgroundDisplaySet = backgroundDisplaySet;
}
if (includeForeground) {
result.foregroundDisplaySets = foregroundDisplaySets;
}
if (includeOverlay) {
result.overlayDisplaySets = overlayDisplaySets;
}
if (includePotentialOverlay) {
result.potentialOverlayDisplaySets = potentialOverlayDisplaySets;
}
if (includePotentialForeground) {
result.potentialForegroundDisplaySets = potentialForegroundDisplaySets;
}
if (includePotentialBackground) {
result.potentialBackgroundDisplaySets = potentialBackgroundDisplaySets;
}
return result;
}