OHIF with Cornerstone3D 2.0 (#4347)
This commit is contained in:
1 parent
8981a61b8c
commit
7ce7127d7c
803 files changed
+32760
-15893
No files matched your search
@@ -48,6 +48,17 @@ export default class ColorbarService extends PubSubService {
|
||||
super(ColorbarService.EVENTS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the volume ID for a given identifier by searching through the viewport's volume IDs.
|
||||
* @param viewport - The viewport instance to search volumes in
|
||||
* @param searchId - The identifier to search for within volume IDs
|
||||
* @returns The matching volume ID if found, null otherwise
|
||||
*/
|
||||
private getVolumeIdForIdentifier(viewport, searchId: string): string | null {
|
||||
const volumeIds = viewport.getAllVolumeIds?.() || [];
|
||||
return volumeIds.length > 0 ? volumeIds.find(id => id.includes(searchId)) || null : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a colorbar to a specific viewport identified by `viewportId`, using the provided `displaySetInstanceUIDs` and `options`.
|
||||
* This method sets up the colorbar, associates it with the viewport, and applies initial configurations based on the provided options.
|
||||
@@ -59,8 +70,18 @@ export default class ColorbarService extends PubSubService {
|
||||
public addColorbar(viewportId, displaySetInstanceUIDs, options = {} as ColorbarOptions) {
|
||||
const renderingEngine = getRenderingEngine(RENDERING_ENGINE_ID);
|
||||
const viewport = renderingEngine.getViewport(viewportId);
|
||||
|
||||
if (!viewport) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { element } = viewport;
|
||||
const actorEntries = viewport.getActors();
|
||||
|
||||
if (!actorEntries || actorEntries.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { position, width: thickness, activeColormapName, colormaps } = options;
|
||||
|
||||
const numContainers = displaySetInstanceUIDs.length;
|
||||
@@ -74,11 +95,9 @@ export default class ColorbarService extends PubSubService {
|
||||
);
|
||||
|
||||
displaySetInstanceUIDs.forEach((displaySetInstanceUID, index) => {
|
||||
const actorEntry = actorEntries.find(entry => entry.uid.includes(displaySetInstanceUID));
|
||||
const volumeId = actorEntry?.uid;
|
||||
const volumeId = this.getVolumeIdForIdentifier(viewport, displaySetInstanceUID);
|
||||
const properties = viewport?.getProperties(volumeId);
|
||||
const colormap = properties?.colormap;
|
||||
// if there's an initial colormap set, and no colormap on the viewport, set it
|
||||
if (activeColormapName && !colormap) {
|
||||
this.setViewportColormap(
|
||||
viewportId,
|
||||
@@ -196,9 +215,8 @@ export default class ColorbarService extends PubSubService {
|
||||
return;
|
||||
}
|
||||
const setViewportProperties = (viewport, uid) => {
|
||||
const actorEntry = actorEntries.find(entry => entry.uid.includes(uid));
|
||||
const { actor: volumeActor, uid: volumeId } = actorEntry;
|
||||
viewport.setProperties({ colormap, volumeActor }, volumeId);
|
||||
const volumeId = this.getVolumeIdForIdentifier(viewport, uid);
|
||||
viewport.setProperties({ colormap }, volumeId);
|
||||
};
|
||||
|
||||
if (viewport instanceof StackViewport) {
|
||||
|
||||
+44
-83
@@ -1,5 +1,5 @@
|
||||
import { Types } from '@ohif/core';
|
||||
import { cache as cs3DCache, Enums, volumeLoader, utilities as utils } from '@cornerstonejs/core';
|
||||
import { cache as cs3DCache, Enums, volumeLoader } from '@cornerstonejs/core';
|
||||
|
||||
import getCornerstoneViewportType from '../../utils/getCornerstoneViewportType';
|
||||
import { StackViewportData, VolumeViewportData } from '../../types/CornerstoneCacheService';
|
||||
@@ -33,27 +33,11 @@ class CornerstoneCacheService {
|
||||
|
||||
public async createViewportData(
|
||||
displaySets: Types.DisplaySet[],
|
||||
viewportOptions: Record<string, unknown>,
|
||||
viewportOptions: AppTypes.ViewportGrid.GridViewportOptions,
|
||||
dataSource: unknown,
|
||||
initialImageIndex?: number
|
||||
): Promise<StackViewportData | VolumeViewportData> {
|
||||
let viewportType = viewportOptions.viewportType as string;
|
||||
|
||||
// Todo: Since Cornerstone 3D currently doesn't support segmentation
|
||||
// on stack viewport, we should check if whether the the displaySets
|
||||
// that are about to be displayed are referenced in a segmentation
|
||||
// as a reference volume, if so, we should hang a volume viewport
|
||||
// instead of a stack viewport
|
||||
if (this._shouldRenderSegmentation(displaySets)) {
|
||||
// if the viewport type is volume 3D, we should let it be as it is
|
||||
// Todo: in future here we should kick start the conversion of the
|
||||
// segmentation to closed surface
|
||||
viewportType =
|
||||
viewportType === Enums.ViewportType.VOLUME_3D ? Enums.ViewportType.VOLUME_3D : 'volume';
|
||||
|
||||
// update viewportOptions to reflect the new viewport type
|
||||
viewportOptions.viewportType = viewportType;
|
||||
}
|
||||
const viewportType = viewportOptions.viewportType as string;
|
||||
|
||||
const cs3DViewportType = getCornerstoneViewportType(viewportType, displaySets);
|
||||
let viewportData: StackViewportData | VolumeViewportData;
|
||||
@@ -86,7 +70,7 @@ class CornerstoneCacheService {
|
||||
}
|
||||
|
||||
public async invalidateViewportData(
|
||||
viewportData: VolumeViewportData,
|
||||
viewportData: VolumeViewportData | StackViewportData,
|
||||
invalidatedDisplaySetInstanceUID: string,
|
||||
dataSource,
|
||||
displaySetService
|
||||
@@ -176,9 +160,6 @@ class CornerstoneCacheService {
|
||||
): Promise<StackViewportData> {
|
||||
const { uiNotificationService } = this.servicesManager.services;
|
||||
const overlayDisplaySets = displaySets.filter(ds => ds.isOverlayDisplaySet);
|
||||
const nonOverlayDisplaySets = displaySets.filter(ds => !ds.isOverlayDisplaySet);
|
||||
|
||||
// load overlays if they are not loaded
|
||||
for (const overlayDisplaySet of overlayDisplaySets) {
|
||||
if (overlayDisplaySet.load && overlayDisplaySet.load instanceof Function) {
|
||||
const { userAuthenticationService } = this.servicesManager.services;
|
||||
@@ -187,7 +168,7 @@ class CornerstoneCacheService {
|
||||
await overlayDisplaySet.load({ headers });
|
||||
} catch (e) {
|
||||
uiNotificationService.show({
|
||||
title: 'Error loading overlayDisplaySet',
|
||||
title: 'Error loading displaySet',
|
||||
message: e.message,
|
||||
type: 'error',
|
||||
});
|
||||
@@ -196,43 +177,43 @@ class CornerstoneCacheService {
|
||||
}
|
||||
}
|
||||
|
||||
const displaySet = nonOverlayDisplaySets[0];
|
||||
|
||||
if (displaySet.load && displaySet.load instanceof Function) {
|
||||
const { userAuthenticationService } = this.servicesManager.services;
|
||||
const headers = userAuthenticationService.getAuthorizationHeader();
|
||||
|
||||
try {
|
||||
await displaySet.load({ headers });
|
||||
} catch (e) {
|
||||
uiNotificationService.show({
|
||||
title: 'Error loading displaySet',
|
||||
message: e.message,
|
||||
type: 'error',
|
||||
});
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
let stackImageIds = this.stackImageIds.get(displaySet.displaySetInstanceUID);
|
||||
|
||||
if (!stackImageIds) {
|
||||
stackImageIds = this._getCornerstoneStackImageIds(displaySet, dataSource);
|
||||
this.stackImageIds.set(displaySet.displaySetInstanceUID, stackImageIds);
|
||||
}
|
||||
|
||||
// Ensuring the first non-overlay `displaySet` is always the primary one
|
||||
const StackViewportData = [displaySet, ...overlayDisplaySets].map(ds => {
|
||||
const { displaySetInstanceUID, StudyInstanceUID, isCompositeStack } = ds;
|
||||
const StackViewportData = [];
|
||||
for (const displaySet of displaySets) {
|
||||
const { displaySetInstanceUID, StudyInstanceUID, isCompositeStack } = displaySet;
|
||||
|
||||
return {
|
||||
if (displaySet.load && displaySet.load instanceof Function) {
|
||||
const { userAuthenticationService } = this.servicesManager.services;
|
||||
const headers = userAuthenticationService.getAuthorizationHeader();
|
||||
try {
|
||||
await displaySet.load({ headers });
|
||||
} catch (e) {
|
||||
uiNotificationService.show({
|
||||
title: 'Error loading displaySet',
|
||||
message: e.message,
|
||||
type: 'error',
|
||||
});
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
let stackImageIds = this.stackImageIds.get(displaySet.displaySetInstanceUID);
|
||||
|
||||
if (!stackImageIds) {
|
||||
stackImageIds = this._getCornerstoneStackImageIds(displaySet, dataSource);
|
||||
// assign imageIds to the displaySet
|
||||
displaySet.imageIds = stackImageIds;
|
||||
this.stackImageIds.set(displaySet.displaySetInstanceUID, stackImageIds);
|
||||
}
|
||||
|
||||
StackViewportData.push({
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
isCompositeStack,
|
||||
imageIds: stackImageIds,
|
||||
initialImageIndex,
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
viewportType,
|
||||
@@ -253,6 +234,7 @@ class CornerstoneCacheService {
|
||||
for (const displaySet of displaySets) {
|
||||
const { Modality } = displaySet;
|
||||
const isParametricMap = Modality === 'PMAP';
|
||||
const isSeg = Modality === 'SEG';
|
||||
|
||||
// Don't create volumes for the displaySets that have custom load
|
||||
// function (e.g., SEG, RT, since they rely on the reference volumes
|
||||
@@ -295,7 +277,7 @@ class CornerstoneCacheService {
|
||||
|
||||
// Parametric maps do not have image ids but they already have volume data
|
||||
// therefore a new volume should not be created.
|
||||
if (!isParametricMap && (!volumeImageIds || !volume)) {
|
||||
if (!isParametricMap && !isSeg && (!volumeImageIds || !volume)) {
|
||||
volumeImageIds = this._getCornerstoneVolumeImageIds(displaySet, dataSource);
|
||||
|
||||
volume = await volumeLoader.createAndCacheVolume(volumeId, {
|
||||
@@ -303,6 +285,9 @@ class CornerstoneCacheService {
|
||||
});
|
||||
|
||||
this.volumeImageIds.set(displaySet.displaySetInstanceUID, volumeImageIds);
|
||||
|
||||
// Add imageIds to the displaySet for volumes
|
||||
displaySet.imageIds = volumeImageIds;
|
||||
}
|
||||
|
||||
volumeData.push({
|
||||
@@ -321,39 +306,15 @@ class CornerstoneCacheService {
|
||||
};
|
||||
}
|
||||
|
||||
private _shouldRenderSegmentation(displaySets) {
|
||||
const { segmentationService, displaySetService } = this.servicesManager.services;
|
||||
|
||||
const viewportDisplaySetInstanceUIDs = displaySets.map(
|
||||
({ displaySetInstanceUID }) => displaySetInstanceUID
|
||||
);
|
||||
|
||||
// check inside segmentations if any of them are referencing the displaySets
|
||||
// that are about to be displayed
|
||||
const segmentations = segmentationService.getSegmentations();
|
||||
|
||||
for (const segmentation of segmentations) {
|
||||
const segDisplaySetInstanceUID = segmentation.displaySetInstanceUID;
|
||||
const segDisplaySet = displaySetService.getDisplaySetByUID(segDisplaySetInstanceUID);
|
||||
|
||||
const instance = segDisplaySet.instances?.[0] || segDisplaySet.instance;
|
||||
|
||||
const shouldDisplaySeg = segmentationService.shouldRenderSegmentation(
|
||||
viewportDisplaySetInstanceUIDs,
|
||||
instance?.FrameOfReferenceUID || segDisplaySet.FrameOfReferenceUID
|
||||
);
|
||||
|
||||
if (shouldDisplaySeg) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _getCornerstoneStackImageIds(displaySet, dataSource): string[] {
|
||||
return dataSource.getImageIdsForDisplaySet(displaySet);
|
||||
}
|
||||
|
||||
private _getCornerstoneVolumeImageIds(displaySet, dataSource): string[] {
|
||||
if (displaySet.imageIds) {
|
||||
return displaySet.imageIds;
|
||||
}
|
||||
|
||||
const stackImageIds = this._getCornerstoneStackImageIds(displaySet, dataSource);
|
||||
|
||||
return stackImageIds;
|
||||
|
||||
+1369
-1728
File diff suppressed because it is too large.
Load diff
@@ -1,76 +0,0 @@
|
||||
import { Enums as csToolsEnums, Types as cstTypes } from '@cornerstonejs/tools';
|
||||
import { Types } from '@cornerstonejs/core';
|
||||
|
||||
type SegmentationConfig = cstTypes.LabelmapTypes.LabelmapConfig & {
|
||||
renderInactiveSegmentations: boolean;
|
||||
brushSize: number;
|
||||
brushThresholdGate: number;
|
||||
};
|
||||
|
||||
type Segment = {
|
||||
// the label for the segment
|
||||
label: string;
|
||||
// the index of the segment in the segmentation
|
||||
segmentIndex: number;
|
||||
// the color of the segment
|
||||
color: Types.Point3;
|
||||
// the opacity of the segment
|
||||
opacity: number;
|
||||
// whether the segment is visible
|
||||
isVisible: boolean;
|
||||
// whether the segment is locked
|
||||
isLocked: boolean;
|
||||
// display texts
|
||||
displayText?: string[];
|
||||
// The name of algorithm used to generate the segment. (0062,0009)
|
||||
algorithmName?: string;
|
||||
// Type of algorithm used to generate the segment. (0062,0008)
|
||||
algorithmType?: string;
|
||||
};
|
||||
|
||||
type Segmentation = {
|
||||
// active segment index is the index of the segment that is currently being edited.
|
||||
activeSegmentIndex: number;
|
||||
// colorLUTIndex is the index of the color LUT that is currently being used.
|
||||
colorLUTIndex: number;
|
||||
// if segmentation contains any data (often calculated from labelmap)
|
||||
cachedStats: Record<string, any>;
|
||||
// displaySetInstanceUID
|
||||
displaySetInstanceUID: string;
|
||||
// displayText is the text that is displayed on the segmentation panel (often derived from the data)
|
||||
displayText?: string[];
|
||||
// the id of the segmentation
|
||||
id: string;
|
||||
// if the segmentation is the active segmentation being used in the viewer
|
||||
isActive: boolean;
|
||||
// if the segmentation is visible in the viewer
|
||||
isVisible: boolean;
|
||||
// the frame of reference UID of the segmentation
|
||||
FrameOfReferenceUID: string;
|
||||
// the label of the segmentation
|
||||
label: string;
|
||||
// the number of segments in the segmentation
|
||||
segmentCount: number;
|
||||
// the array of segments with their details, [null, segment1, segment2, ...]
|
||||
segments: Array<Segment>;
|
||||
// the set of segments that are locked
|
||||
segmentsLocked: Array<number>;
|
||||
// whether the segmentation is hydrated or not (non-hydrated SEG -> temporary segmentation for display in SEG Viewport
|
||||
// but hydrated SEG -> segmentation that is persisted in the store)
|
||||
hydrated: boolean;
|
||||
// the type of the segmentation (e.g., Labelmap etc.)
|
||||
type: csToolsEnums.SegmentationRepresentations;
|
||||
// the segmentation representation data
|
||||
representationData: SegmentationRepresentationData;
|
||||
};
|
||||
|
||||
type LabelmapSegmentationData = {
|
||||
volumeId: string;
|
||||
referencedVolumeId?: string;
|
||||
};
|
||||
|
||||
type SegmentationRepresentationData = {
|
||||
LABELMAP?: LabelmapSegmentationData;
|
||||
};
|
||||
|
||||
export type { SegmentationConfig, Segment, Segmentation };
|
||||
File renamed without changes.
@@ -2,6 +2,7 @@ import { synchronizers, SynchronizerManager, Synchronizer } from '@cornerstonejs
|
||||
import { getRenderingEngines, utilities } from '@cornerstonejs/core';
|
||||
|
||||
import { pubSubServiceInterface, Types } from '@ohif/core';
|
||||
import createHydrateSegmentationSynchronizer from './createHydrateSegmentationSynchronizer';
|
||||
|
||||
const EVENTS = {
|
||||
TOOL_GROUP_CREATED: 'event::cornerstone::syncgroupservice:toolgroupcreated',
|
||||
@@ -27,6 +28,7 @@ const VOI = 'voi';
|
||||
const ZOOMPAN = 'zoompan';
|
||||
const STACKIMAGE = 'stackimage';
|
||||
const IMAGE_SLICE = 'imageslice';
|
||||
const HYDRATE_SEG = 'hydrateseg';
|
||||
|
||||
const asSyncGroup = (syncGroup: string | SyncGroup): SyncGroup =>
|
||||
typeof syncGroup === 'string' ? { type: syncGroup } : syncGroup;
|
||||
@@ -51,6 +53,7 @@ export default class SyncGroupService {
|
||||
// handles both stack and volume viewports
|
||||
[STACKIMAGE]: synchronizers.createImageSliceSynchronizer,
|
||||
[IMAGE_SLICE]: synchronizers.createImageSliceSynchronizer,
|
||||
[HYDRATE_SEG]: createHydrateSegmentationSynchronizer,
|
||||
};
|
||||
|
||||
synchronizersByType: { [key: string]: Synchronizer[] } = {};
|
||||
@@ -66,18 +69,18 @@ export default class SyncGroupService {
|
||||
private _createSynchronizer(type: string, id: string, options): Synchronizer | undefined {
|
||||
// Initialize if not already done
|
||||
this.synchronizersByType[type] = this.synchronizersByType[type] || [];
|
||||
|
||||
const syncCreator = this.synchronizerCreators[type.toLowerCase()];
|
||||
|
||||
if (syncCreator) {
|
||||
const synchronizer = syncCreator(id, options);
|
||||
// Pass the servicesManager along with other parameters
|
||||
const synchronizer = syncCreator(id, { ...options, servicesManager: this.servicesManager });
|
||||
|
||||
if (synchronizer) {
|
||||
this.synchronizersByType[type].push(synchronizer);
|
||||
return synchronizer;
|
||||
}
|
||||
} else {
|
||||
console.warn(`Unknown synchronizer type: ${type}, id: ${id}`);
|
||||
console.debug(`Unknown synchronizer type: ${type}, id: ${id}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
import { Types, getEnabledElementByViewportId } from '@cornerstonejs/core';
|
||||
import {
|
||||
SynchronizerManager,
|
||||
Synchronizer,
|
||||
Enums,
|
||||
Types as ToolsTypes,
|
||||
} from '@cornerstonejs/tools';
|
||||
|
||||
const { createSynchronizer } = SynchronizerManager;
|
||||
const { SEGMENTATION_REPRESENTATION_ADDED } = Enums.Events;
|
||||
|
||||
export default function createHydrateSegmentationSynchronizer(
|
||||
synchronizerName: string,
|
||||
{ servicesManager, ...options }: { servicesManager: AppTypes.ServicesManager; options }
|
||||
): Synchronizer {
|
||||
const stackImageSynchronizer = createSynchronizer(
|
||||
synchronizerName,
|
||||
SEGMENTATION_REPRESENTATION_ADDED,
|
||||
(synchronizerInstance, sourceViewport, targetViewport, sourceEvent) =>
|
||||
segmentationRepresentationModifiedCallback(
|
||||
synchronizerInstance,
|
||||
sourceViewport,
|
||||
targetViewport,
|
||||
sourceEvent,
|
||||
{ servicesManager, options }
|
||||
),
|
||||
{
|
||||
eventSource: 'eventTarget',
|
||||
}
|
||||
);
|
||||
|
||||
return stackImageSynchronizer;
|
||||
}
|
||||
|
||||
const segmentationRepresentationModifiedCallback = async (
|
||||
synchronizerInstance: Synchronizer,
|
||||
sourceViewport: Types.IViewportId,
|
||||
targetViewport: Types.IViewportId,
|
||||
sourceEvent: Event,
|
||||
{ servicesManager, options }: { servicesManager: AppTypes.ServicesManager; options: unknown }
|
||||
) => {
|
||||
const event = sourceEvent as ToolsTypes.EventTypes.SegmentationRepresentationModifiedEventType;
|
||||
|
||||
const { segmentationId, viewportId } = event.detail;
|
||||
const { segmentationService, hangingProtocolService } = servicesManager.services;
|
||||
|
||||
const targetViewportId = targetViewport.viewportId;
|
||||
|
||||
const { viewport } = getEnabledElementByViewportId(targetViewportId);
|
||||
|
||||
const targetFrameOfReferenceUID = viewport.getFrameOfReferenceUID();
|
||||
|
||||
if (!targetFrameOfReferenceUID) {
|
||||
console.debug('No frame of reference UID found for the target viewport');
|
||||
return;
|
||||
}
|
||||
|
||||
const targetViewportRepresentation = segmentationService.getSegmentationRepresentations(
|
||||
targetViewportId,
|
||||
{ segmentationId }
|
||||
);
|
||||
|
||||
if (targetViewportRepresentation.length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// whatever type the source viewport has, we need to add that to the target viewport
|
||||
const sourceViewportRepresentation = segmentationService.getSegmentationRepresentations(
|
||||
sourceViewport.viewportId,
|
||||
{ segmentationId }
|
||||
);
|
||||
|
||||
const type = sourceViewportRepresentation[0].type;
|
||||
|
||||
await segmentationService.addSegmentationRepresentation(targetViewportId, {
|
||||
segmentationId,
|
||||
type,
|
||||
});
|
||||
};
|
||||
+14
-14
@@ -7,7 +7,7 @@ export type ActionComponentInfo = {
|
||||
id: string;
|
||||
component: ReactNode;
|
||||
location: ViewportActionCornersLocations;
|
||||
indexPriority: number;
|
||||
indexPriority?: number;
|
||||
};
|
||||
|
||||
class ViewportActionCornersService extends PubSubService {
|
||||
@@ -33,21 +33,21 @@ class ViewportActionCornersService extends PubSubService {
|
||||
|
||||
public setServiceImplementation({
|
||||
getState: getStateImplementation,
|
||||
setComponent: setComponentImplementation,
|
||||
setComponents: setComponentsImplementation,
|
||||
clear: clearImplementation,
|
||||
addComponent: addComponentImplementation,
|
||||
addComponents: addComponentsImplementation,
|
||||
clear: clearComponentsImplementation,
|
||||
}): void {
|
||||
if (getStateImplementation) {
|
||||
this.serviceImplementation._getState = getStateImplementation;
|
||||
}
|
||||
if (setComponentImplementation) {
|
||||
this.serviceImplementation._setComponent = setComponentImplementation;
|
||||
if (addComponentImplementation) {
|
||||
this.serviceImplementation._addComponent = addComponentImplementation;
|
||||
}
|
||||
if (setComponentsImplementation) {
|
||||
this.serviceImplementation._setComponents = setComponentsImplementation;
|
||||
if (addComponentsImplementation) {
|
||||
this.serviceImplementation._addComponents = addComponentsImplementation;
|
||||
}
|
||||
if (clearImplementation) {
|
||||
this.serviceImplementation._clear = clearImplementation;
|
||||
if (clearComponentsImplementation) {
|
||||
this.serviceImplementation._clear = clearComponentsImplementation;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,12 +55,12 @@ class ViewportActionCornersService extends PubSubService {
|
||||
return this.serviceImplementation._getState();
|
||||
}
|
||||
|
||||
public setComponent(component: ActionComponentInfo) {
|
||||
this.serviceImplementation._setComponent(component);
|
||||
public addComponent(component: ActionComponentInfo) {
|
||||
this.serviceImplementation._addComponent(component);
|
||||
}
|
||||
|
||||
public setComponents(components: Array<ActionComponentInfo>) {
|
||||
this.serviceImplementation._setComponents(components);
|
||||
public addComponents(components: Array<ActionComponentInfo>) {
|
||||
this.serviceImplementation._addComponents(components);
|
||||
}
|
||||
|
||||
public clear(viewportId: string) {
|
||||
|
||||
+234
-268
@@ -18,9 +18,20 @@ import { IViewportService } from './IViewportService';
|
||||
import { RENDERING_ENGINE_ID } from './constants';
|
||||
import ViewportInfo, { DisplaySetOptions, PublicViewportOptions } from './Viewport';
|
||||
import { StackViewportData, VolumeViewportData } from '../../types/CornerstoneCacheService';
|
||||
import { LutPresentation, PositionPresentation, Presentations } from '../../types/Presentation';
|
||||
import {
|
||||
LutPresentation,
|
||||
PositionPresentation,
|
||||
Presentations,
|
||||
SegmentationPresentation,
|
||||
SegmentationPresentationItem,
|
||||
} from '../../types/Presentation';
|
||||
|
||||
import JumpPresets from '../../utils/JumpPresets';
|
||||
import { ViewportProperties } from '@cornerstonejs/core/types';
|
||||
import { useLutPresentationStore } from '../../stores/useLutPresentationStore';
|
||||
import { usePositionPresentationStore } from '../../stores/usePositionPresentationStore';
|
||||
import { useSynchronizersStore } from '../../stores/useSynchronizersStore';
|
||||
import { useSegmentationPresentationStore } from '../../stores/useSegmentationPresentationStore';
|
||||
|
||||
const EVENTS = {
|
||||
VIEWPORT_DATA_CHANGED: 'event::cornerstoneViewportService:viewportDataChanged',
|
||||
@@ -176,11 +187,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
* @param presentations - The presentations to apply to the viewport.
|
||||
* @param viewportInfo - Contains a view reference for immediate application
|
||||
*/
|
||||
public setPresentations(
|
||||
viewportId: string,
|
||||
presentations?: Presentations,
|
||||
viewportInfo?: ViewportInfo
|
||||
): void {
|
||||
public setPresentations(viewportId: string, presentations: Presentations): void {
|
||||
const viewport = this.getCornerstoneViewport(viewportId) as
|
||||
| Types.IStackViewport
|
||||
| Types.IVolumeViewport;
|
||||
@@ -189,83 +196,113 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
return;
|
||||
}
|
||||
|
||||
const { lutPresentation, positionPresentation } = presentations;
|
||||
if (lutPresentation) {
|
||||
const { presentation } = lutPresentation;
|
||||
if (viewport instanceof BaseVolumeViewport) {
|
||||
if (presentation instanceof Map) {
|
||||
presentation.forEach((properties, volumeId) => {
|
||||
viewport.setProperties(properties, volumeId);
|
||||
});
|
||||
} else {
|
||||
viewport.setProperties(presentation);
|
||||
}
|
||||
} else {
|
||||
viewport.setProperties(presentation);
|
||||
}
|
||||
const { lutPresentation, positionPresentation, segmentationPresentation } = presentations;
|
||||
|
||||
// Always set the segmentation presentation first, since there might be some
|
||||
// lutpresentation states that need to be set on the segmentation
|
||||
// Todo: i think we should even await this
|
||||
this._setSegmentationPresentation(viewport, segmentationPresentation);
|
||||
|
||||
this._setLutPresentation(viewport, lutPresentation);
|
||||
this._setPositionPresentation(viewport, { ...positionPresentation, viewportId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the presentation state for a given viewport inside the
|
||||
* each store. This is used to persist the presentation state
|
||||
* across different scenarios e.g., when the viewport is changing the
|
||||
* display set, or when the viewport is moving to a different layout.
|
||||
*
|
||||
* @param viewportId The ID of the viewport.
|
||||
*/
|
||||
public storePresentation({ viewportId }) {
|
||||
const presentationIds = this.getPresentationIds(viewportId);
|
||||
const { syncGroupService } = this.servicesManager.services;
|
||||
const synchronizers = syncGroupService.getSynchronizersForViewport(viewportId);
|
||||
|
||||
if (!presentationIds || Object.keys(presentationIds).length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const viewRef = viewportInfo?.getViewReference() || positionPresentation?.viewReference;
|
||||
if (viewRef) {
|
||||
viewport.setViewReference(viewRef);
|
||||
const { lutPresentationId, positionPresentationId, segmentationPresentationId } =
|
||||
presentationIds;
|
||||
|
||||
const positionPresentation = this._getPositionPresentation(viewportId);
|
||||
const lutPresentation = this._getLutPresentation(viewportId);
|
||||
const segmentationPresentation = this._getSegmentationPresentation(viewportId);
|
||||
|
||||
const { setLutPresentation } = useLutPresentationStore.getState();
|
||||
const { setPositionPresentation } = usePositionPresentationStore.getState();
|
||||
const { setSynchronizers } = useSynchronizersStore.getState();
|
||||
const { setSegmentationPresentation } = useSegmentationPresentationStore.getState();
|
||||
|
||||
if (lutPresentationId) {
|
||||
setLutPresentation(lutPresentationId, lutPresentation);
|
||||
}
|
||||
if (positionPresentation?.position) {
|
||||
viewport.setViewPresentation(positionPresentation.position);
|
||||
|
||||
if (positionPresentationId) {
|
||||
setPositionPresentation(positionPresentationId, positionPresentation);
|
||||
}
|
||||
|
||||
if (segmentationPresentationId) {
|
||||
setSegmentationPresentation(segmentationPresentationId, segmentationPresentation);
|
||||
}
|
||||
|
||||
if (synchronizers?.length) {
|
||||
setSynchronizers(
|
||||
viewportId,
|
||||
synchronizers.map(synchronizer => ({
|
||||
id: synchronizer.id,
|
||||
sourceViewports: [...synchronizer.getSourceViewports()],
|
||||
targetViewports: [...synchronizer.getTargetViewports()],
|
||||
}))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the position presentation information for a given viewport.
|
||||
* @param viewportId The ID of the viewport.
|
||||
* @returns The position presentation object containing various properties
|
||||
* such as ID, viewport type, initial image index, view plane normal, view up, zoom, and pan.
|
||||
* Retrieves the presentations for a given viewport.
|
||||
* @param viewportId - The ID of the viewport.
|
||||
* @returns The presentations for the viewport.
|
||||
*/
|
||||
public getPositionPresentation(viewportId: string): PositionPresentation {
|
||||
public getPresentations(viewportId: string): Presentations {
|
||||
const positionPresentation = this._getPositionPresentation(viewportId);
|
||||
const lutPresentation = this._getLutPresentation(viewportId);
|
||||
const segmentationPresentation = this._getSegmentationPresentation(viewportId);
|
||||
|
||||
return {
|
||||
positionPresentation,
|
||||
lutPresentation,
|
||||
segmentationPresentation,
|
||||
};
|
||||
}
|
||||
|
||||
private getPresentationIds(viewportId: string): AppTypes.PresentationIds | null {
|
||||
const viewportInfo = this.viewportsById.get(viewportId);
|
||||
if (!viewportInfo) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
const presentationIds = viewportInfo.getPresentationIds();
|
||||
|
||||
if (!presentationIds) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { positionPresentationId } = presentationIds;
|
||||
return viewportInfo.getPresentationIds();
|
||||
}
|
||||
|
||||
private _getPositionPresentation(viewportId: string): PositionPresentation {
|
||||
const csViewport = this.getCornerstoneViewport(viewportId);
|
||||
if (!csViewport) {
|
||||
return;
|
||||
}
|
||||
|
||||
const viewportInfo = this.viewportsById.get(viewportId);
|
||||
|
||||
return {
|
||||
id: positionPresentationId,
|
||||
viewportType: viewportInfo.getViewportType(),
|
||||
viewReference: csViewport instanceof VolumeViewport3D ? null : csViewport.getViewReference(),
|
||||
position: csViewport.getViewPresentation({ pan: true, zoom: true }),
|
||||
viewPresentation: csViewport.getViewPresentation({ pan: true, zoom: true }),
|
||||
viewportId,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the LUT (Lookup Table) presentation for a given viewport.
|
||||
* @param viewportId The ID of the viewport.
|
||||
* @returns The LUT presentation object, or undefined if the viewport does not exist.
|
||||
*/
|
||||
public getLutPresentation(viewportId: string): LutPresentation {
|
||||
const viewportInfo = this.viewportsById.get(viewportId);
|
||||
if (!viewportInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
const presentationIds = viewportInfo.getPresentationIds();
|
||||
|
||||
if (!presentationIds) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { lutPresentationId } = presentationIds;
|
||||
|
||||
private _getLutPresentation(viewportId: string): LutPresentation {
|
||||
const csViewport = this.getCornerstoneViewport(viewportId) as
|
||||
| Types.IStackViewport
|
||||
| Types.IVolumeViewport;
|
||||
@@ -275,116 +312,39 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
}
|
||||
|
||||
const cleanProperties = properties => {
|
||||
if (properties.isComputedVOI) {
|
||||
delete properties.voiRange;
|
||||
delete properties.VOILUTFunction;
|
||||
if (properties?.isComputedVOI) {
|
||||
delete properties?.voiRange;
|
||||
delete properties?.VOILUTFunction;
|
||||
}
|
||||
return properties;
|
||||
};
|
||||
|
||||
const presentation =
|
||||
const properties =
|
||||
csViewport instanceof BaseVolumeViewport
|
||||
? new Map()
|
||||
: cleanProperties(csViewport.getProperties());
|
||||
|
||||
if (presentation instanceof Map) {
|
||||
csViewport.getActors().forEach(({ uid: volumeId }) => {
|
||||
const properties = cleanProperties(csViewport.getProperties(volumeId));
|
||||
presentation.set(volumeId, properties);
|
||||
if (properties instanceof Map) {
|
||||
const volumeIds = (csViewport as Types.IBaseVolumeViewport).getAllVolumeIds();
|
||||
volumeIds?.forEach(volumeId => {
|
||||
const csProps = cleanProperties(csViewport.getProperties(volumeId));
|
||||
properties.set(volumeId, csProps);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
id: lutPresentationId,
|
||||
viewportType: viewportInfo.getViewportType(),
|
||||
presentation,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the presentations for a given viewport.
|
||||
* @param viewportId - The ID of the viewport.
|
||||
* @returns The presentations for the viewport.
|
||||
*/
|
||||
public getPresentations(viewportId: string): Presentations {
|
||||
const viewportInfo = this.viewportsById.get(viewportId);
|
||||
if (!viewportInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
const positionPresentation = this.getPositionPresentation(viewportId);
|
||||
const lutPresentation = this.getLutPresentation(viewportId);
|
||||
|
||||
return {
|
||||
positionPresentation,
|
||||
lutPresentation,
|
||||
viewportType: viewportInfo.getViewportType(),
|
||||
properties,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the presentation state for a given viewport inside the
|
||||
* stateSyncService. This is used to persist the presentation state
|
||||
* across different scenarios e.g., when the viewport is changing the
|
||||
* display set, or when the viewport is moving to a different layout.
|
||||
*
|
||||
* @param viewportId The ID of the viewport.
|
||||
*/
|
||||
public storePresentation({ viewportId }) {
|
||||
let presentations = null as Presentations;
|
||||
try {
|
||||
presentations = this.getPresentations(viewportId);
|
||||
if (!presentations?.positionPresentation && !presentations?.lutPresentation) {
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(error);
|
||||
return;
|
||||
}
|
||||
private _getSegmentationPresentation(viewportId: string): SegmentationPresentation {
|
||||
const { segmentationService } = this.servicesManager.services;
|
||||
|
||||
const { stateSyncService, syncGroupService } = this.servicesManager.services;
|
||||
|
||||
const synchronizers = syncGroupService.getSynchronizersForViewport(viewportId);
|
||||
|
||||
const { positionPresentationStore, synchronizersStore, lutPresentationStore } =
|
||||
stateSyncService.getState();
|
||||
|
||||
const { lutPresentation, positionPresentation } = presentations;
|
||||
const { id: positionPresentationId } = positionPresentation;
|
||||
const { id: lutPresentationId } = lutPresentation;
|
||||
|
||||
const updateStore = (store, id, value) => ({ ...store, [id]: value });
|
||||
|
||||
const newState = {} as { [key: string]: any };
|
||||
|
||||
if (lutPresentationId) {
|
||||
newState.lutPresentationStore = updateStore(
|
||||
lutPresentationStore,
|
||||
lutPresentationId,
|
||||
lutPresentation
|
||||
);
|
||||
}
|
||||
|
||||
if (positionPresentationId) {
|
||||
newState.positionPresentationStore = updateStore(
|
||||
positionPresentationStore,
|
||||
positionPresentationId,
|
||||
positionPresentation
|
||||
);
|
||||
}
|
||||
|
||||
if (synchronizers?.length) {
|
||||
newState.synchronizersStore = updateStore(
|
||||
synchronizersStore,
|
||||
viewportId,
|
||||
synchronizers.map(synchronizer => ({
|
||||
id: synchronizer.id,
|
||||
sourceViewports: [...synchronizer.getSourceViewports()],
|
||||
targetViewports: [...synchronizer.getTargetViewports()],
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
stateSyncService.store(newState);
|
||||
const presentation = segmentationService.getPresentation(viewportId);
|
||||
return presentation;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -423,6 +383,11 @@ 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');
|
||||
}
|
||||
@@ -431,8 +396,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
// since those are the newly set ones, we set them here so that it handles defaults
|
||||
const displaySetOptions = viewportInfo.setPublicDisplaySetOptions(publicDisplaySetOptions);
|
||||
// Specify an over-ride for the viewport type, even though it is in the public
|
||||
// viewport options, because the one in the public viewport options is a suggestion
|
||||
// for initial view, whereas the one in viewportData is a requirement based on the
|
||||
// viewport options, because the one in the viewportData is a requirement based on the
|
||||
// type of data being displayed.
|
||||
const viewportOptions = viewportInfo.setPublicViewportOptions(
|
||||
publicViewportOptions,
|
||||
@@ -647,13 +611,21 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
properties.colormap = colormap ?? properties.colormap;
|
||||
}
|
||||
|
||||
this._handleOverlays(viewport);
|
||||
viewport.element.addEventListener(csEnums.Events.VIEWPORT_NEW_IMAGE_SET, evt => {
|
||||
const { element } = evt.detail;
|
||||
|
||||
if (!imageIds?.length) {
|
||||
return;
|
||||
}
|
||||
if (element !== viewport.element) {
|
||||
return;
|
||||
}
|
||||
|
||||
return viewport.setStack(imageIds, initialImageIndexToUse).then(() => {
|
||||
csToolsUtils.stackContextPrefetch.enable(element);
|
||||
});
|
||||
|
||||
let imageIdsToSet = imageIds;
|
||||
const res = this._processExtraDisplaySetsForViewport(viewport);
|
||||
imageIdsToSet = res?.imageIds ?? imageIdsToSet;
|
||||
|
||||
return viewport.setStack(imageIdsToSet, initialImageIndexToUse).then(() => {
|
||||
viewport.setProperties({ ...properties });
|
||||
this.setPresentations(viewport.id, presentations, viewportInfo);
|
||||
if (displayArea) {
|
||||
@@ -702,7 +674,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
const lastSliceIndex = numberOfSlices - 1;
|
||||
|
||||
if (imageIndex !== undefined) {
|
||||
return csToolsUtils.clip(imageIndex, 0, lastSliceIndex);
|
||||
return csUtils.clip(imageIndex, 0, lastSliceIndex);
|
||||
}
|
||||
|
||||
if (preset === JumpPresets.First) {
|
||||
@@ -764,7 +736,6 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
|
||||
const displaySetOptions = displaySetOptionsArray[index];
|
||||
const { volumeId } = volume;
|
||||
|
||||
volumeInputArray.push({
|
||||
imageIds,
|
||||
volumeId,
|
||||
@@ -775,8 +746,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
|
||||
this.viewportsDisplaySets.set(viewport.id, displaySetInstanceUIDs);
|
||||
|
||||
const volumesNotLoaded = volumeToLoad.filter(volume => !volume.loadStatus.loaded);
|
||||
|
||||
const volumesNotLoaded = volumeToLoad.filter(volume => !volume.loadStatus?.loaded);
|
||||
if (volumesNotLoaded.length) {
|
||||
if (hangingProtocolService.getShouldPerformCustomImageLoad()) {
|
||||
// delegate the volume loading to the hanging protocol service if it has a custom image load strategy
|
||||
@@ -787,7 +757,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
}
|
||||
|
||||
volumesNotLoaded.forEach(volume => {
|
||||
if (!volume.loadStatus.loading) {
|
||||
if (!volume.loadStatus?.loading && volume.load instanceof Function) {
|
||||
volume.load();
|
||||
}
|
||||
});
|
||||
@@ -798,8 +768,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
}
|
||||
|
||||
public async setVolumesForViewport(viewport, volumeInputArray, presentations) {
|
||||
const { displaySetService, toolGroupService, viewportGridService } =
|
||||
this.servicesManager.services;
|
||||
const { displaySetService, viewportGridService } = this.servicesManager.services;
|
||||
|
||||
const viewportInfo = this.getViewportInfo(viewport.id);
|
||||
const displaySetOptions = viewportInfo.getDisplaySetOptions();
|
||||
@@ -811,7 +780,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
const { volumeId } = volumeInput;
|
||||
const displaySetOption = displaySetOptions[index];
|
||||
const { voi, voiInverted, colormap, displayPreset } = displaySetOption;
|
||||
const properties = {};
|
||||
const properties = {} as ViewportProperties;
|
||||
|
||||
if (voi && (voi.windowWidth || voi.windowCenter)) {
|
||||
const { lower, upper } = csUtils.windowLevel.toLowHighRange(
|
||||
@@ -836,22 +805,21 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
return { properties, volumeId };
|
||||
});
|
||||
|
||||
// For SEG and RT viewports
|
||||
this._processExtraDisplaySetsForViewport(viewport);
|
||||
|
||||
await viewport.setVolumes(volumeInputArray);
|
||||
|
||||
volumesProperties.forEach(({ properties, volumeId }) => {
|
||||
viewport.setProperties(properties, volumeId);
|
||||
});
|
||||
|
||||
this.setPresentations(viewport.id, presentations, viewportInfo);
|
||||
|
||||
this._handleOverlays(viewport);
|
||||
|
||||
const toolGroup = toolGroupService.getToolGroupForViewport(viewport.id);
|
||||
csToolsUtils.segmentation.triggerSegmentationRender(toolGroup.id);
|
||||
|
||||
const imageIndex = this._getInitialImageIndexForViewport(viewportInfo);
|
||||
|
||||
if (imageIndex !== undefined) {
|
||||
csToolsUtils.jumpToSlice(viewport.element, {
|
||||
csUtils.jumpToSlice(viewport.element, {
|
||||
imageIndex,
|
||||
});
|
||||
}
|
||||
@@ -863,7 +831,9 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
});
|
||||
}
|
||||
|
||||
private _handleOverlays(viewport: Types.IStackViewport | Types.IVolumeViewport) {
|
||||
private _processExtraDisplaySetsForViewport(
|
||||
viewport: Types.IStackViewport | Types.IVolumeViewport
|
||||
) {
|
||||
const { displaySetService } = this.servicesManager.services;
|
||||
|
||||
// load any secondary displaySets
|
||||
@@ -877,104 +847,41 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
displaySet?.isOverlayDisplaySet && ['SEG', 'RTSTRUCT'].includes(displaySet.Modality)
|
||||
);
|
||||
|
||||
if (segOrRTSOverlayDisplaySet) {
|
||||
this.addOverlayRepresentationForDisplaySet(segOrRTSOverlayDisplaySet, viewport);
|
||||
} else {
|
||||
// If the displaySet is not a SEG displaySet we assume it is a primary displaySet
|
||||
// and we can look into hydrated segmentations to check if any of them are
|
||||
// associated with the primary displaySet
|
||||
|
||||
// get segmentations only returns the hydrated segmentations
|
||||
this._addSegmentationRepresentationToToolGroupIfNecessary(displaySetInstanceUIDs, viewport);
|
||||
// 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) {
|
||||
return;
|
||||
}
|
||||
|
||||
const referenceDisplaySet = displaySetService.getDisplaySetByUID(
|
||||
segOrRTSOverlayDisplaySet.referencedDisplaySetInstanceUID
|
||||
);
|
||||
const imageIds = referenceDisplaySet.images.map(image => image.imageId);
|
||||
this.addOverlayRepresentationForDisplaySet(segOrRTSOverlayDisplaySet, viewport);
|
||||
return { imageIds };
|
||||
}
|
||||
|
||||
private _addSegmentationRepresentationToToolGroupIfNecessary(
|
||||
displaySetInstanceUIDs: string[],
|
||||
viewport: any
|
||||
private addOverlayRepresentationForDisplaySet(
|
||||
displaySet: OhifTypes.DisplaySet,
|
||||
viewport: Types.IViewport
|
||||
) {
|
||||
const { segmentationService, toolGroupService } = this.servicesManager.services;
|
||||
|
||||
const toolGroup = toolGroupService.getToolGroupForViewport(viewport.id);
|
||||
|
||||
// this only returns hydrated segmentations
|
||||
const segmentations = segmentationService.getSegmentations();
|
||||
|
||||
for (const segmentation of segmentations) {
|
||||
const toolGroupSegmentationRepresentations =
|
||||
segmentationService.getSegmentationRepresentationsForToolGroup(toolGroup.id) || [];
|
||||
|
||||
// if there is already a segmentation representation for this segmentation
|
||||
// for this toolGroup, don't bother at all
|
||||
const isSegmentationInToolGroup = toolGroupSegmentationRepresentations.find(
|
||||
representation => representation.segmentationId === segmentation.id
|
||||
);
|
||||
|
||||
if (isSegmentationInToolGroup) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// otherwise, check if the hydrated segmentations are in the same FrameOfReferenceUID
|
||||
// as the primary displaySet, if so add the representation (since it was not there)
|
||||
const { id: segDisplaySetInstanceUID } = segmentation;
|
||||
let segFrameOfReferenceUID = this._getFrameOfReferenceUID(segDisplaySetInstanceUID);
|
||||
|
||||
if (!segFrameOfReferenceUID) {
|
||||
// if the segmentation displaySet does not have a FrameOfReferenceUID, we might check the
|
||||
// segmentation itself maybe it has a FrameOfReferenceUID
|
||||
const { FrameOfReferenceUID } = segmentation;
|
||||
if (FrameOfReferenceUID) {
|
||||
segFrameOfReferenceUID = FrameOfReferenceUID;
|
||||
}
|
||||
}
|
||||
|
||||
if (!segFrameOfReferenceUID) {
|
||||
return;
|
||||
}
|
||||
|
||||
let shouldDisplaySeg = false;
|
||||
|
||||
for (const displaySetInstanceUID of displaySetInstanceUIDs) {
|
||||
const primaryFrameOfReferenceUID = this._getFrameOfReferenceUID(displaySetInstanceUID);
|
||||
|
||||
if (segFrameOfReferenceUID === primaryFrameOfReferenceUID) {
|
||||
shouldDisplaySeg = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!shouldDisplaySeg) {
|
||||
return;
|
||||
}
|
||||
|
||||
segmentationService.addSegmentationRepresentationToToolGroup(
|
||||
toolGroup.id,
|
||||
segmentation.id,
|
||||
false, // already hydrated,
|
||||
segmentation.type
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private addOverlayRepresentationForDisplaySet(displaySet: any, viewport: any) {
|
||||
const { segmentationService, toolGroupService } = this.servicesManager.services;
|
||||
|
||||
const { referencedVolumeId } = displaySet;
|
||||
const { segmentationService } = this.servicesManager.services;
|
||||
const segmentationId = displaySet.displaySetInstanceUID;
|
||||
|
||||
const toolGroup = toolGroupService.getToolGroupForViewport(viewport.id);
|
||||
|
||||
const representationType =
|
||||
referencedVolumeId && cache.getVolume(referencedVolumeId) !== undefined
|
||||
displaySet.Modality === 'SEG'
|
||||
? csToolsEnums.SegmentationRepresentations.Labelmap
|
||||
: csToolsEnums.SegmentationRepresentations.Contour;
|
||||
|
||||
segmentationService.addSegmentationRepresentationToToolGroup(
|
||||
toolGroup.id,
|
||||
segmentationService.addSegmentationRepresentation(viewport.id, {
|
||||
segmentationId,
|
||||
false,
|
||||
representationType
|
||||
);
|
||||
type: representationType,
|
||||
});
|
||||
|
||||
// store the segmentation presentation id in the viewport info
|
||||
this.storePresentation({ viewportId: viewport.id });
|
||||
}
|
||||
|
||||
// Todo: keepCamera is an interim solution until we have a better solution for
|
||||
@@ -1032,7 +939,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
}
|
||||
|
||||
return this._setOtherViewport(
|
||||
viewport as Types.IViewport,
|
||||
viewport,
|
||||
viewportData as StackViewportData,
|
||||
viewportInfo,
|
||||
presentations
|
||||
@@ -1063,11 +970,11 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
// calculate the slab thickness based on the volume dimensions
|
||||
const imageVolume = cache.getVolume(volumeId);
|
||||
|
||||
const { dimensions } = imageVolume;
|
||||
const { dimensions, spacing } = imageVolume;
|
||||
const slabThickness = Math.sqrt(
|
||||
dimensions[0] * dimensions[0] +
|
||||
dimensions[1] * dimensions[1] +
|
||||
dimensions[2] * dimensions[2]
|
||||
Math.pow(dimensions[0] * spacing[0], 2) +
|
||||
Math.pow(dimensions[1] * spacing[1], 2) +
|
||||
Math.pow(dimensions[2] * spacing[2], 2)
|
||||
);
|
||||
|
||||
return slabThickness;
|
||||
@@ -1128,9 +1035,9 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
const viewports = this.getRenderingEngine().getViewports();
|
||||
|
||||
// Store the current position presentations for each viewport.
|
||||
viewports.forEach(({ id }) => {
|
||||
const presentation = this.getPositionPresentation(id);
|
||||
this.beforeResizePositionPresentations.set(id, presentation);
|
||||
viewports.forEach(({ id: viewportId }) => {
|
||||
const presentation = this._getPositionPresentation(viewportId);
|
||||
this.beforeResizePositionPresentations.set(viewportId, presentation);
|
||||
});
|
||||
|
||||
// Resize the rendering engine and render.
|
||||
@@ -1159,6 +1066,65 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
this.gridResizeTimeOut = null;
|
||||
}, this.gridResizeDelay);
|
||||
}
|
||||
|
||||
private _setLutPresentation(
|
||||
viewport: Types.IStackViewport | Types.IVolumeViewport,
|
||||
lutPresentation: LutPresentation
|
||||
): void {
|
||||
if (!lutPresentation) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { properties } = lutPresentation;
|
||||
if (viewport instanceof BaseVolumeViewport) {
|
||||
if (properties instanceof Map) {
|
||||
properties.forEach((propertiesEntry, volumeId) => {
|
||||
viewport.setProperties(propertiesEntry, volumeId);
|
||||
});
|
||||
} else {
|
||||
viewport.setProperties(properties);
|
||||
}
|
||||
} else {
|
||||
viewport.setProperties(properties);
|
||||
}
|
||||
}
|
||||
|
||||
private _setPositionPresentation(
|
||||
viewport: Types.IStackViewport | Types.IVolumeViewport,
|
||||
positionPresentation: PositionPresentation
|
||||
): void {
|
||||
const viewRef = positionPresentation?.viewReference;
|
||||
if (viewRef) {
|
||||
viewport.setViewReference(viewRef);
|
||||
}
|
||||
|
||||
const viewPresentation = positionPresentation?.viewPresentation;
|
||||
if (viewPresentation) {
|
||||
viewport.setViewPresentation(viewPresentation);
|
||||
}
|
||||
}
|
||||
|
||||
private _setSegmentationPresentation(
|
||||
viewport: Types.IStackViewport | Types.IVolumeViewport,
|
||||
segmentationPresentation: SegmentationPresentation
|
||||
): void {
|
||||
if (!segmentationPresentation) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { segmentationService } = this.servicesManager.services;
|
||||
|
||||
segmentationPresentation.forEach((presentation: SegmentationPresentationItem) => {
|
||||
const { segmentationId, type, hydrated } = presentation;
|
||||
|
||||
if (hydrated) {
|
||||
segmentationService.addSegmentationRepresentation(viewport.id, {
|
||||
segmentationId,
|
||||
type,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default CornerstoneViewportService;
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
VolumeViewport,
|
||||
utilities,
|
||||
} from '@cornerstonejs/core';
|
||||
import { Types as CoreTypes } from '@ohif/core';
|
||||
import { StackViewportData, VolumeViewportData } from '../../types/CornerstoneCacheService';
|
||||
import getCornerstoneBlendMode from '../../utils/getCornerstoneBlendMode';
|
||||
import getCornerstoneOrientation from '../../utils/getCornerstoneOrientation';
|
||||
@@ -25,7 +24,7 @@ export type ViewportOptions = {
|
||||
toolGroupId: string;
|
||||
viewportId: string;
|
||||
// Presentation ID to store/load presentation state from
|
||||
presentationIds?: CoreTypes.PresentationIds;
|
||||
presentationIds?: AppTypes.PresentationIds;
|
||||
orientation?: Enums.OrientationAxis;
|
||||
background?: Types.Point3;
|
||||
displayArea?: Types.DisplayArea;
|
||||
@@ -46,7 +45,7 @@ export type PublicViewportOptions = {
|
||||
id?: string;
|
||||
viewportType?: string;
|
||||
toolGroupId?: string;
|
||||
presentationIds?: CoreTypes.PresentationIds;
|
||||
presentationIds?: string[];
|
||||
viewportId?: string;
|
||||
orientation?: Enums.OrientationAxis;
|
||||
background?: Types.Point3;
|
||||
@@ -283,7 +282,7 @@ class ViewportInfo {
|
||||
return this.viewportOptions;
|
||||
}
|
||||
|
||||
public getPresentationIds(): CoreTypes.PresentationIds {
|
||||
public getPresentationIds(): AppTypes.PresentationIds | null {
|
||||
const { presentationIds } = this.viewportOptions;
|
||||
return presentationIds;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user