feat(data overlay): add foreground background control plus orientation change in the UI and add RTDOSE support (#5007)
This commit is contained in:
1 parent
c1fced0573
commit
cd4e6408bc
105 files changed
+3117
-788
No files matched your search
@@ -38,14 +38,16 @@ export default class ColorbarService extends PubSubService {
|
||||
|
||||
public static REGISTRATION = {
|
||||
name: 'colorbarService',
|
||||
create: () => {
|
||||
return new ColorbarService();
|
||||
create: ({ servicesManager }: OhifTypes.Extensions.ExtensionParams) => {
|
||||
return new ColorbarService(servicesManager);
|
||||
},
|
||||
};
|
||||
colorbars = {};
|
||||
servicesManager: AppTypes.ServicesManager;
|
||||
|
||||
constructor() {
|
||||
constructor(servicesManager: AppTypes.ServicesManager) {
|
||||
super(ColorbarService.EVENTS);
|
||||
this.servicesManager = servicesManager;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,6 +70,7 @@ export default class ColorbarService extends PubSubService {
|
||||
* @param options Configuration options for the colorbar, including position, colormaps, active colormap name, ticks, and width.
|
||||
*/
|
||||
public addColorbar(viewportId, displaySetInstanceUIDs, options = {} as ColorbarOptions) {
|
||||
const { displaySetService } = this.servicesManager.services;
|
||||
const renderingEngine = getRenderingEngine(RENDERING_ENGINE_ID);
|
||||
const viewport = renderingEngine.getViewport(viewportId);
|
||||
|
||||
@@ -95,6 +98,12 @@ export default class ColorbarService extends PubSubService {
|
||||
);
|
||||
|
||||
displaySetInstanceUIDs.forEach((displaySetInstanceUID, index) => {
|
||||
// don't show colorbar for overlay display sets (e.g. segmentation)
|
||||
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
||||
if (displaySet.isOverlayDisplaySet) {
|
||||
return;
|
||||
}
|
||||
|
||||
const volumeId = this.getVolumeIdForIdentifier(viewport, displaySetInstanceUID);
|
||||
const properties = viewport?.getProperties(volumeId);
|
||||
const colormap = properties?.colormap;
|
||||
@@ -123,9 +132,19 @@ export default class ColorbarService extends PubSubService {
|
||||
volumeId: viewport instanceof VolumeViewport ? volumeId : undefined,
|
||||
});
|
||||
if (this.colorbars[viewportId]) {
|
||||
this.colorbars[viewportId].push({ colorbar, container: colorbarContainer });
|
||||
this.colorbars[viewportId].push({
|
||||
colorbar,
|
||||
container: colorbarContainer,
|
||||
displaySetInstanceUID,
|
||||
});
|
||||
} else {
|
||||
this.colorbars[viewportId] = [{ colorbar, container: colorbarContainer }];
|
||||
this.colorbars[viewportId] = [
|
||||
{
|
||||
colorbar,
|
||||
container: colorbarContainer,
|
||||
displaySetInstanceUID,
|
||||
},
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
@@ -136,24 +155,50 @@ export default class ColorbarService extends PubSubService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the colorbar associated with a given viewport ID. This involves cleaning up any created DOM elements and internal references.
|
||||
* Removes a colorbar from a specific viewport. If displaySetInstanceUID is provided,
|
||||
* only the colorbar associated with that specific displaySetInstanceUID will be removed.
|
||||
* Otherwise, all colorbars for the given viewport will be removed.
|
||||
*
|
||||
* @param viewportId The identifier for the viewport from which the colorbar will be removed.
|
||||
* @param displaySetInstanceUID Optional. The specific display set instance UID associated with the colorbar to remove.
|
||||
*/
|
||||
public removeColorbar(viewportId) {
|
||||
public removeColorbar(viewportId, displaySetInstanceUID?: string) {
|
||||
const colorbarInfo = this.colorbars[viewportId];
|
||||
if (!colorbarInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
colorbarInfo.forEach(({ colorbar, container }) => {
|
||||
container.parentNode.removeChild(container);
|
||||
});
|
||||
if (displaySetInstanceUID) {
|
||||
// Find the index of the colorbar with the matching displaySetInstanceUID
|
||||
const index = colorbarInfo.findIndex(
|
||||
info => info.displaySetInstanceUID === displaySetInstanceUID
|
||||
);
|
||||
|
||||
delete this.colorbars[viewportId];
|
||||
if (index !== -1) {
|
||||
// Remove only the specific colorbar container
|
||||
const { container } = colorbarInfo[index];
|
||||
container.parentNode.removeChild(container);
|
||||
|
||||
// Remove the colorbar from the array
|
||||
colorbarInfo.splice(index, 1);
|
||||
|
||||
// If there are no more colorbars for this viewport, remove the entry
|
||||
if (colorbarInfo.length === 0) {
|
||||
delete this.colorbars[viewportId];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Remove all colorbars for the viewport (original behavior)
|
||||
colorbarInfo.forEach(({ container }) => {
|
||||
container.parentNode.removeChild(container);
|
||||
});
|
||||
|
||||
delete this.colorbars[viewportId];
|
||||
}
|
||||
|
||||
this._broadcastEvent(ColorbarService.EVENTS.STATE_CHANGED, {
|
||||
viewportId,
|
||||
displaySetInstanceUID,
|
||||
changeType: ChangeTypes.Removed,
|
||||
});
|
||||
}
|
||||
|
||||
+1
-2
@@ -3,8 +3,7 @@ import { cache as cs3DCache, Enums, volumeLoader } from '@cornerstonejs/core';
|
||||
|
||||
import getCornerstoneViewportType from '../../utils/getCornerstoneViewportType';
|
||||
import { StackViewportData, VolumeViewportData } from '../../types/CornerstoneCacheService';
|
||||
|
||||
const VOLUME_LOADER_SCHEME = 'cornerstoneStreamingImageVolume';
|
||||
import { VOLUME_LOADER_SCHEME } from '../../constants';
|
||||
|
||||
class CornerstoneCacheService {
|
||||
static REGISTRATION = {
|
||||
|
||||
+21
-18
@@ -8,26 +8,29 @@
|
||||
* and geometry Id
|
||||
*/
|
||||
export function mapROIContoursToRTStructData(structureSet: unknown, rtDisplaySetUID: unknown) {
|
||||
return structureSet.ROIContours.map(({ contourPoints, ROINumber, ROIName, colorArray }) => {
|
||||
const data = contourPoints.map(({ points, ...rest }) => {
|
||||
const newPoints = points.map(({ x, y, z }) => {
|
||||
return [x, y, z];
|
||||
return structureSet.ROIContours.map(
|
||||
({ contourPoints, ROINumber, ROIName, colorArray, ROIGroup }) => {
|
||||
const data = contourPoints.map(({ points, ...rest }) => {
|
||||
const newPoints = points.map(({ x, y, z }) => {
|
||||
return [x, y, z];
|
||||
});
|
||||
|
||||
return {
|
||||
...rest,
|
||||
points: newPoints,
|
||||
};
|
||||
});
|
||||
|
||||
const id = ROIName || ROINumber;
|
||||
|
||||
return {
|
||||
...rest,
|
||||
points: newPoints,
|
||||
data,
|
||||
id,
|
||||
segmentIndex: ROINumber,
|
||||
color: colorArray,
|
||||
group: ROIGroup,
|
||||
geometryId: `${rtDisplaySetUID}:${id}:segmentIndex-${ROINumber}`,
|
||||
};
|
||||
});
|
||||
|
||||
const id = ROIName || ROINumber;
|
||||
|
||||
return {
|
||||
data,
|
||||
id,
|
||||
segmentIndex: ROINumber,
|
||||
color: colorArray,
|
||||
geometryId: `${rtDisplaySetUID}:${id}:segmentIndex-${ROINumber}`,
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import { updateLabelmapSegmentationImageReferences } from '@cornerstonejs/tools/
|
||||
import { triggerSegmentationRepresentationModified } from '@cornerstonejs/tools/segmentation/triggerSegmentationEvents';
|
||||
import { convertStackToVolumeLabelmap } from '@cornerstonejs/tools/segmentation/helpers/convertStackToVolumeLabelmap';
|
||||
import { getLabelmapImageIds } from '@cornerstonejs/tools/segmentation';
|
||||
import { VOLUME_LOADER_SCHEME } from '../../constants';
|
||||
|
||||
const LABELMAP = csToolsEnums.SegmentationRepresentations.Labelmap;
|
||||
const CONTOUR = csToolsEnums.SegmentationRepresentations.Contour;
|
||||
@@ -80,8 +81,6 @@ const EVENTS = {
|
||||
|
||||
const VALUE_TYPES = {};
|
||||
|
||||
const VOLUME_LOADER_SCHEME = 'cornerstoneStreamingImageVolume';
|
||||
|
||||
class SegmentationService extends PubSubService {
|
||||
static REGISTRATION = {
|
||||
name: 'segmentationService',
|
||||
@@ -615,7 +614,7 @@ class SegmentationService extends PubSubService {
|
||||
|
||||
// Process each segment similarly to the SEG function
|
||||
for (const rtStructData of allRTStructData) {
|
||||
const { data, id, color, segmentIndex, geometryId } = rtStructData;
|
||||
const { data, id, color, segmentIndex, geometryId, group } = rtStructData;
|
||||
|
||||
// Add the color to the colorLUT array
|
||||
colorLUT.push(color);
|
||||
@@ -646,6 +645,7 @@ class SegmentationService extends PubSubService {
|
||||
cachedStats: segmentsCachedStats,
|
||||
locked: false,
|
||||
active: false,
|
||||
group,
|
||||
};
|
||||
|
||||
// Broadcast segment loading progress
|
||||
|
||||
+21
@@ -10,6 +10,11 @@ export type ActionComponentInfo = {
|
||||
indexPriority?: number;
|
||||
};
|
||||
|
||||
export type AlignAndSide = {
|
||||
align: 'start' | 'end' | 'center';
|
||||
side: 'top' | 'bottom' | 'left' | 'right';
|
||||
};
|
||||
|
||||
class ViewportActionCornersService extends PubSubService {
|
||||
public static readonly EVENTS = {};
|
||||
public static readonly LOCATIONS = ViewportActionCornersLocations;
|
||||
@@ -66,6 +71,22 @@ class ViewportActionCornersService extends PubSubService {
|
||||
public clear(viewportId: string) {
|
||||
this.serviceImplementation._clear(viewportId);
|
||||
}
|
||||
|
||||
public getAlignAndSide(location: ViewportActionCornersLocations): AlignAndSide {
|
||||
switch (location) {
|
||||
case ViewportActionCornersLocations.topLeft:
|
||||
return { align: 'start', side: 'bottom' };
|
||||
case ViewportActionCornersLocations.topRight:
|
||||
return { align: 'end', side: 'bottom' };
|
||||
case ViewportActionCornersLocations.bottomLeft:
|
||||
return { align: 'start', side: 'top' };
|
||||
case ViewportActionCornersLocations.bottomRight:
|
||||
return { align: 'end', side: 'top' };
|
||||
default:
|
||||
console.debug('Unknown location, defaulting to bottom-start');
|
||||
return { align: 'start', side: 'bottom' };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ViewportActionCornersService;
|
||||
@@ -32,6 +32,7 @@ import { useLutPresentationStore } from '../../stores/useLutPresentationStore';
|
||||
import { usePositionPresentationStore } from '../../stores/usePositionPresentationStore';
|
||||
import { useSynchronizersStore } from '../../stores/useSynchronizersStore';
|
||||
import { useSegmentationPresentationStore } from '../../stores/useSegmentationPresentationStore';
|
||||
import { VOLUME_LOADER_SCHEME } from '../../constants';
|
||||
|
||||
const EVENTS = {
|
||||
VIEWPORT_DATA_CHANGED: 'event::cornerstoneViewportService:viewportDataChanged',
|
||||
@@ -407,11 +408,6 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
// and we would lose the presentation.
|
||||
this.storePresentation({ viewportId: viewportInfo.getViewportId() });
|
||||
|
||||
// Todo: i don't like this here, move it
|
||||
this.servicesManager.services.segmentationService.clearSegmentationRepresentations(
|
||||
viewportInfo.getViewportId()
|
||||
);
|
||||
|
||||
if (!viewportInfo) {
|
||||
throw new Error('element is not enabled for the given viewportId');
|
||||
}
|
||||
@@ -513,6 +509,11 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
return this.viewportsById.get(viewportId);
|
||||
}
|
||||
|
||||
public getOrientation(viewportId: string): string {
|
||||
const viewportInfo = this.getViewportInfo(viewportId);
|
||||
return viewportInfo.getOrientation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks through the viewports to see if the specified measurement can be
|
||||
* displayed in one of the viewports. This function tries to get a "best fit"
|
||||
@@ -761,8 +762,8 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
const { imageIds, displaySetInstanceUID } = data;
|
||||
let volume = data.volume;
|
||||
|
||||
if (!volume) {
|
||||
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
||||
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
||||
if (!volume && displaySet.images) {
|
||||
volume = csToolsUtils.getOrCreateImageVolume(displaySet.images.map(image => image.imageId));
|
||||
}
|
||||
|
||||
@@ -780,6 +781,8 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
volumeInputArray.push({
|
||||
imageIds,
|
||||
volumeId,
|
||||
modality: displaySet.Modality,
|
||||
displaySetInstanceUID,
|
||||
blendMode: displaySetOptions.blendMode,
|
||||
slabThickness: this._getSlabThickness(displaySetOptions, volumeId),
|
||||
});
|
||||
@@ -822,8 +825,15 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
const displaySetUIDs = viewportGridService.getDisplaySetsUIDsForViewport(viewport.id);
|
||||
const displaySet = displaySetService.getDisplaySetByUID(displaySetUIDs[0]);
|
||||
const displaySetModality = displaySet?.Modality;
|
||||
|
||||
// filter overlay display sets (e.g. segmentation) since they will get handled below via the segmentation service
|
||||
const filteredVolumeInputArray = volumeInputArray.filter(volumeInput => {
|
||||
const displaySet = displaySetService.getDisplaySetByUID(volumeInput.displaySetInstanceUID);
|
||||
return !displaySet?.isOverlayDisplaySet;
|
||||
});
|
||||
|
||||
// Todo: use presentations states
|
||||
const volumesProperties = volumeInputArray.map((volumeInput, index) => {
|
||||
const volumesProperties = filteredVolumeInputArray.map((volumeInput, index) => {
|
||||
const { volumeId } = volumeInput;
|
||||
const displaySetOption = displaySetOptions[index];
|
||||
const { voi, voiInverted, colormap, displayPreset } = displaySetOption;
|
||||
@@ -853,9 +863,29 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
});
|
||||
|
||||
// For SEG and RT viewports
|
||||
const { addOverlayFn } = this._processExtraDisplaySetsForViewport(viewport) || {};
|
||||
const { addOverlayFn, imageIds } = this._processExtraDisplaySetsForViewport(viewport) || {};
|
||||
|
||||
await viewport.setVolumes(volumeInputArray);
|
||||
if (!filteredVolumeInputArray.length && addOverlayFn) {
|
||||
// if there is no volume input array, and there is an addOverlayFn, means we need to take
|
||||
// care of the background overlay display set first then the addOverlayFn will add the
|
||||
// SEG displaySet
|
||||
const sampleImageId = imageIds[0];
|
||||
const backgroundDisplaySet = displaySetService.getDisplaySetsBy(
|
||||
displaySet =>
|
||||
!displaySet.isOverlayDisplaySet &&
|
||||
displaySet.images.some(image => image.imageId === sampleImageId)
|
||||
);
|
||||
|
||||
if (backgroundDisplaySet.length !== 1) {
|
||||
throw new Error('Background display set not found');
|
||||
}
|
||||
|
||||
await viewport.setVolumes([
|
||||
{ volumeId: `${VOLUME_LOADER_SCHEME}:${backgroundDisplaySet[0].displaySetInstanceUID}` },
|
||||
]);
|
||||
} else {
|
||||
await viewport.setVolumes(filteredVolumeInputArray);
|
||||
}
|
||||
|
||||
if (addOverlayFn) {
|
||||
addOverlayFn();
|
||||
@@ -896,31 +926,30 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
// load any secondary displaySets
|
||||
const displaySetInstanceUIDs = this.viewportsDisplaySets.get(viewport.id);
|
||||
|
||||
// Can be SEG or RTSTRUCT for now but not PMAP
|
||||
const segOrRTSOverlayDisplaySet = displaySetInstanceUIDs
|
||||
// Find overlay display sets (e.g. SEG, RTSTRUCT)
|
||||
const overlayDisplaySet = displaySetInstanceUIDs
|
||||
.map(displaySetService.getDisplaySetByUID)
|
||||
.find(
|
||||
displaySet =>
|
||||
displaySet?.isOverlayDisplaySet && ['SEG', 'RTSTRUCT'].includes(displaySet.Modality)
|
||||
);
|
||||
.find(displaySet => displaySet?.isOverlayDisplaySet);
|
||||
|
||||
// if it is only the overlay displaySet, then we need to get the reference
|
||||
// displaySet imageIds and set them as the imageIds for the viewport,
|
||||
// here we can do some logic if the reference is missing
|
||||
// then find the most similar match of displaySet instead
|
||||
if (!segOrRTSOverlayDisplaySet) {
|
||||
if (!overlayDisplaySet) {
|
||||
return;
|
||||
}
|
||||
|
||||
const referenceDisplaySet = displaySetService.getDisplaySetByUID(
|
||||
segOrRTSOverlayDisplaySet.referencedDisplaySetInstanceUID
|
||||
);
|
||||
const imageIds = referenceDisplaySet.images.map(image => image.imageId);
|
||||
let imageIds;
|
||||
if (overlayDisplaySet.referencedDisplaySetInstanceUID) {
|
||||
const referenceDisplaySet = displaySetService.getDisplaySetByUID(
|
||||
overlayDisplaySet.referencedDisplaySetInstanceUID
|
||||
);
|
||||
imageIds = referenceDisplaySet.images.map(image => image.imageId);
|
||||
}
|
||||
|
||||
return {
|
||||
imageIds,
|
||||
addOverlayFn: () =>
|
||||
this.addOverlayRepresentationForDisplaySet(segOrRTSOverlayDisplaySet, viewport),
|
||||
addOverlayFn: () => this.addOverlayRepresentationForDisplaySet(overlayDisplaySet, viewport),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -316,6 +316,10 @@ class ViewportInfo {
|
||||
return this.viewportOptions.orientation;
|
||||
}
|
||||
|
||||
public setOrientation(orientation: Enums.OrientationAxis): void {
|
||||
this.viewportOptions.orientation = orientation;
|
||||
}
|
||||
|
||||
public getDisplayArea(): Types.DisplayArea {
|
||||
return this.viewportOptions.displayArea;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user