feat(grid): remove viewportIndex and only rely on viewportId (#3591)

Co-authored-by: Bill Wallace <wayfarer3130@gmail.com>
This commit is contained in:
AlirezaandBill Wallace authored and GitHub committed 2023-08-30 16:46:55 -04:00
1 parent 5dafac7c92
commit 4c6ff873e8
83 files changed
+1309 -1123

No files matched your search

@@ -9,8 +9,6 @@ import {
VolumeViewport,
VolumeViewport3D,
cache,
utilities,
CONSTANTS,
Enums as csEnums,
} from '@cornerstonejs/core';
@@ -55,7 +53,6 @@ class CornerstoneViewportService extends PubSubService
};
renderingEngine: Types.IRenderingEngine | null;
viewportsInfo: Map<number, ViewportInfo> = new Map();
viewportsById: Map<string, ViewportInfo> = new Map();
viewportGridResizeObserver: ResizeObserver | null;
viewportsDisplaySets: Map<string, string[]> = new Map();
@@ -75,45 +72,17 @@ class CornerstoneViewportService extends PubSubService
/**
* Adds the HTML element to the viewportService
* @param {*} viewportIndex
* @param {*} viewportId
* @param {*} elementRef
*/
public enableViewport(
viewportIndex: number,
viewportOptions: PublicViewportOptions,
elementRef: HTMLDivElement
): void {
// Use the provided viewportId
// Not providing a viewportId is frowned upon because it does weird things
// on moving them around, but it does mostly work.
if (!viewportOptions.viewportId) {
console.warn('Should provide viewport id externally', viewportOptions);
viewportOptions.viewportId =
this.getViewportId(viewportIndex) || `viewport-${viewportIndex}`;
}
const { viewportId } = viewportOptions;
const viewportInfo = new ViewportInfo(viewportIndex, viewportId);
if (!viewportInfo.viewportId) {
throw new Error('Should have viewport ID afterwards');
}
public enableViewport(viewportId: string, elementRef: HTMLDivElement): void {
const viewportInfo = new ViewportInfo(viewportId);
viewportInfo.setElement(elementRef);
this.viewportsInfo.set(viewportIndex, viewportInfo);
this.viewportsById.set(viewportId, viewportInfo);
}
public getViewportIds(): string[] {
const viewportIds = [];
this.viewportsInfo.forEach(viewportInfo => {
viewportIds.push(viewportInfo.getViewportId());
});
return viewportIds;
}
public getViewportId(viewportIndex: number): string {
return this.viewportsInfo[viewportIndex]?.viewportId;
return Array.from(this.viewportsById.keys());
}
/**
@@ -171,21 +140,14 @@ class CornerstoneViewportService extends PubSubService
* created for every new viewport, this will be called whenever the set of
* viewports is changed, but NOT when the viewport position changes only.
*
* @param viewportIndex
* @param viewportId - The viewportId to disable
*/
public disableElement(viewportIndex: number): void {
const viewportInfo = this.viewportsInfo.get(viewportIndex);
if (!viewportInfo) {
return;
}
public disableElement(viewportId: string): void {
this.renderingEngine?.disableElement(viewportId);
const viewportId = viewportInfo.getViewportId();
this.renderingEngine && this.renderingEngine.disableElement(viewportId);
this.viewportsInfo.get(viewportIndex).destroy();
this.viewportsInfo.delete(viewportIndex);
// clean up
this.viewportsById.delete(viewportId);
this.viewportsDisplaySets.delete(viewportId);
}
public setPresentations(viewport, presentations?: Presentations): void {
@@ -199,14 +161,14 @@ class CornerstoneViewportService extends PubSubService
}
}
public getPresentation(viewportIndex: number): Presentation {
const viewportInfo = this.viewportsInfo.get(viewportIndex);
public getPresentation(viewportId: string): Presentation {
const viewportInfo = this.viewportsById.get(viewportId);
if (!viewportInfo) {
return;
}
const { viewportType, presentationIds } = viewportInfo.getViewportOptions();
const csViewport = this.getCornerstoneViewportByIndex(viewportIndex);
const csViewport = this.getCornerstoneViewport(viewportId);
if (!csViewport) {
return;
}
@@ -228,55 +190,80 @@ class CornerstoneViewportService extends PubSubService
};
}
public storePresentation({ viewportId }) {
const stateSyncService = this.servicesManager.services.stateSyncService;
let presentation;
try {
presentation = this.getPresentation(viewportId);
} catch (error) {
console.warn(error);
}
if (!presentation || !presentation.presentationIds) {
return;
}
const {
lutPresentationStore,
positionPresentationStore,
} = stateSyncService.getState();
const { presentationIds } = presentation;
const { lutPresentationId, positionPresentationId } = presentationIds || {};
const storeState = {};
if (lutPresentationId) {
storeState.lutPresentationStore = {
...lutPresentationStore,
[lutPresentationId]: presentation,
};
}
if (positionPresentationId) {
storeState.positionPresentationStore = {
...positionPresentationStore,
[positionPresentationId]: presentation,
};
}
stateSyncService.store(storeState);
}
/**
* Uses the renderingEngine to enable the element for the given viewport index
* and sets the displaySet data to the viewport
* @param {*} viewportIndex
* @param {*} displaySet
* @param {*} dataSource
* @returns
* Sets the viewport data for a viewport.
* @param viewportId - The ID of the viewport to set the data for.
* @param viewportData - The viewport data to set.
* @param publicViewportOptions - The public viewport options.
* @param publicDisplaySetOptions - The public display set options.
* @param presentations - The presentations to set.
*/
public setViewportData(
viewportIndex: number,
viewportId: string,
viewportData: StackViewportData | VolumeViewportData,
publicViewportOptions: PublicViewportOptions,
publicDisplaySetOptions: DisplaySetOptions[],
presentations?: Presentations
): void {
const renderingEngine = this.getRenderingEngine();
const viewportId =
publicViewportOptions.viewportId || this.getViewportId(viewportIndex);
if (!viewportId) {
throw new Error('Must define viewportId externally');
}
// This is the old viewportInfo, which may have old options but we might be
// using its viewport (same viewportId as the new viewportInfo)
const viewportInfo = this.viewportsById.get(viewportId);
// We should store the presentation for the current viewport since we can't only
// rely to store it WHEN the viewport is disabled since we might keep around the
// same viewport/element and just change the viewportData for it (drag and drop etc.)
// the disableElement storePresentation handle would not be called in this case
// and we would lose the presentation.
this.storePresentation({ viewportId: viewportInfo.getViewportId() });
if (!viewportInfo) {
throw new Error('Viewport info not defined');
throw new Error('element is not enabled for the given viewportId');
}
// If the viewport has moved index, then record the new index
if (viewportInfo.viewportIndex !== viewportIndex) {
this.viewportsInfo.delete(viewportInfo.viewportIndex);
this.viewportsInfo.set(viewportIndex, viewportInfo);
viewportInfo.viewportIndex = viewportIndex;
}
viewportInfo.setRenderingEngineId(renderingEngine.id);
const {
viewportOptions,
displaySetOptions,
} = this._getViewportAndDisplaySetOptions(
publicViewportOptions,
publicDisplaySetOptions,
viewportInfo
// override the viewportOptions and displaySetOptions with the public ones
// since those are the newly set ones, we set them here so that it handles defaults
const displaySetOptions = viewportInfo.setPublicDisplaySetOptions(
publicDisplaySetOptions
);
const viewportOptions = viewportInfo.setPublicViewportOptions(
publicViewportOptions
);
viewportInfo.setViewportOptions(viewportOptions);
viewportInfo.setDisplaySetOptions(displaySetOptions);
viewportInfo.setViewportData(viewportData);
const element = viewportInfo.getElement();
const type = viewportInfo.getViewportType();
@@ -293,12 +280,26 @@ class CornerstoneViewportService extends PubSubService
},
};
// Rendering Engine Id set should happen before enabling the element
// since there are callbacks that depend on the renderingEngine id
// Todo: however, this is a limitation which means that we can't change
// the rendering engine id for a given viewport which might be a super edge
// case
viewportInfo.setRenderingEngineId(renderingEngine.id);
// Todo: this is not optimal at all, we are re-enabling the already enabled
// element which is not what we want. But enabledElement as part of the
// renderingEngine is designed to be used like this. This will trigger
// ENABLED_ELEMENT again and again, which will run onEnableElement callbacks
renderingEngine.enableElement(viewportInput);
viewportInfo.setViewportOptions(viewportOptions);
viewportInfo.setDisplaySetOptions(displaySetOptions);
viewportInfo.setViewportData(viewportData);
viewportInfo.setViewportId(viewportId);
this.viewportsById.set(viewportId, viewportInfo);
const viewport = renderingEngine.getViewport(viewportId);
this._setDisplaySets(viewport, viewportData, viewportInfo, presentations);
@@ -307,7 +308,6 @@ class CornerstoneViewportService extends PubSubService
// invalid data.
this._broadcastEvent(this.EVENTS.VIEWPORT_DATA_CHANGED, {
viewportData,
viewportIndex,
viewportId,
});
}
@@ -330,43 +330,8 @@ class CornerstoneViewportService extends PubSubService
return viewport;
}
public getCornerstoneViewportByIndex(
viewportIndex: number
): Types.IStackViewport | Types.IVolumeViewport | null {
const viewportInfo = this.getViewportInfoByIndex(viewportIndex);
if (
!viewportInfo ||
!this.renderingEngine ||
this.renderingEngine.hasBeenDestroyed
) {
return null;
}
const viewport = this.renderingEngine.getViewport(
viewportInfo.getViewportId()
);
return viewport;
}
/**
* Returns the viewportIndex for the provided viewportId
* @param {string} viewportId - the viewportId
* @returns {number} - the viewportIndex
*/
public getViewportInfoByIndex(viewportIndex: number): ViewportInfo {
return this.viewportsInfo.get(viewportIndex);
}
public getViewportInfo(viewportId: string): ViewportInfo {
// @ts-ignore
for (const [index, viewport] of this.viewportsInfo.entries()) {
if (viewport.getViewportId() === viewportId) {
return viewport;
}
}
return null;
return this.viewportsById.get(viewportId);
}
_setStackViewport(
@@ -414,7 +379,7 @@ class CornerstoneViewportService extends PubSubService
}
viewport.setStack(imageIds, initialImageIndexToUse).then(() => {
viewport.setProperties(properties);
viewport.setProperties({ ...properties });
const camera = presentations.positionPresentation?.camera;
if (camera) {
viewport.setCamera(camera);
@@ -541,10 +506,7 @@ class CornerstoneViewportService extends PubSubService
this.viewportsDisplaySets.set(viewport.id, displaySetInstanceUIDs);
if (
hangingProtocolService.hasCustomImageLoadStrategy() &&
!hangingProtocolService.customImageLoadPerformed
) {
if (hangingProtocolService.getShouldPerformCustomImageLoad()) {
// delegate the volume loading to the hanging protocol service if it has a custom image load strategy
return hangingProtocolService.runImageLoadStrategy({
viewportId: viewport.id,
@@ -744,14 +706,8 @@ class CornerstoneViewportService extends PubSubService
// Todo: keepCamera is an interim solution until we have a better solution for
// keeping the camera position when the viewport data is changed
public updateViewport(
viewportIndex: number,
viewportData,
keepCamera = false
) {
const viewportInfo = this.getViewportInfoByIndex(viewportIndex);
const viewportId = viewportInfo.getViewportId();
public updateViewport(viewportId: string, viewportData, keepCamera = false) {
const viewportInfo = this.getViewportInfo(viewportId);
const viewport = this.getCornerstoneViewport(viewportId);
const viewportCamera = viewport.getCamera();
@@ -841,36 +797,6 @@ class CornerstoneViewportService extends PubSubService
}
}
_getViewportAndDisplaySetOptions(
publicViewportOptions: PublicViewportOptions,
publicDisplaySetOptions: DisplaySetOptions[],
viewportInfo: ViewportInfo
): {
viewportOptions: ViewportOptions;
displaySetOptions: DisplaySetOptions[];
} {
const viewportIndex = viewportInfo.getViewportIndex();
// Creating a temporary viewportInfo to handle defaults
const newViewportInfo = new ViewportInfo(
viewportIndex,
viewportInfo.getViewportId()
);
// To handle setting the default values if missing for the viewportOptions and
// displaySetOptions
newViewportInfo.setPublicViewportOptions(publicViewportOptions);
newViewportInfo.setPublicDisplaySetOptions(publicDisplaySetOptions);
const newViewportOptions = newViewportInfo.getViewportOptions();
const newDisplaySetOptions = newViewportInfo.getDisplaySetOptions();
return {
viewportOptions: newViewportOptions,
displaySetOptions: newDisplaySetOptions,
};
}
_getFrameOfReferenceUID(displaySetInstanceUID) {
const { displaySetService } = this.servicesManager.services;
const displaySet = displaySetService.getDisplaySetByUID(
@@ -907,25 +833,25 @@ class CornerstoneViewportService extends PubSubService
*
* @param measurement
* The measurement that is desired to view.
* @param activeViewportIndex - the index that was active at the time the jump
* @param activeViewportId - the index that was active at the time the jump
* was initiated.
* @return the viewportIndex to display the given measurement
* @return the viewportId that the measurement should be displayed in.
*/
public getViewportIndexToJump(
activeViewportIndex: number,
public getViewportIdToJump(
activeViewportId: string,
displaySetInstanceUID: string,
cameraProps: unknown
): number {
const viewportInfo = this.viewportsInfo.get(activeViewportIndex);
): string {
const viewportInfo = this.getViewportInfo(activeViewportId);
const { referencedImageId } = cameraProps;
if (viewportInfo?.contains(displaySetInstanceUID, referencedImageId)) {
return activeViewportIndex;
return activeViewportId;
}
return (
[...this.viewportsById.values()].find(viewportInfo =>
viewportInfo.contains(displaySetInstanceUID, referencedImageId)
)?.viewportIndex ?? -1
)?.viewportId ?? null
);
}
}
@@ -17,7 +17,6 @@ export interface IViewportService {
viewportGridResizeObserver: unknown;
viewportsInfo: unknown;
sceneVolumeInputs: unknown;
viewportIndexUIDMap: unknown;
viewportDivElements: unknown;
ViewportPropertiesMap: unknown;
volumeUIDs: unknown;
@@ -28,14 +27,9 @@ export interface IViewportService {
_broadcastEvent: unknown;
/**
* Adds the HTML element to the viewportService
* @param {*} viewportIndex
* @param {*} elementRef
*/
enableViewport(
viewportIndex: number,
viewportOptions: ViewportOptions,
elementRef: HTMLDivElement
): void;
enableViewport(viewportId: string, elementRef: HTMLDivElement): void;
/**
* It retrieves the renderingEngine if it does exist, or creates one otherwise
* @returns {RenderingEngine} rendering engine
@@ -54,19 +48,17 @@ export interface IViewportService {
/**
* Disables the viewport inside the renderingEngine, if no viewport is left
* it destroys the renderingEngine.
* @param viewportIndex
* @param viewportId
*/
disableElement(viewportIndex: number): void;
disableElement(viewportId: string): void;
/**
* Uses the renderingEngine to enable the element for the given viewport index
* and sets the displaySet data to the viewport
* @param {*} viewportIndex
* @param {*} displaySet
* @param {*} dataSource
* @returns
*/
setViewportData(
viewportIndex: number,
viewportData: StackData | VolumeData,
publicViewportOptions: PublicViewportOptions,
publicDisplaySetOptions: DisplaySetOptions[]
@@ -106,15 +106,13 @@ const dataContains = (
class ViewportInfo {
private viewportId = '';
private viewportIndex: number;
private element: HTMLDivElement;
private viewportOptions: ViewportOptions;
private displaySetOptions: Array<DisplaySetOptions>;
private viewportData: StackViewportData | VolumeViewportData;
private renderingEngineId: string;
constructor(viewportIndex: number, viewportId: string) {
this.viewportIndex = viewportIndex;
constructor(viewportId: string) {
this.viewportId = viewportId;
this.setPublicViewportOptions({});
this.setPublicDisplaySetOptions([{}]);
@@ -155,9 +153,6 @@ class ViewportInfo {
public setViewportId(viewportId: string): void {
this.viewportId = viewportId;
}
public setViewportIndex(viewportIndex: number): void {
this.viewportIndex = viewportIndex;
}
public setElement(element: HTMLDivElement): void {
this.element = element;
@@ -173,9 +168,6 @@ class ViewportInfo {
return this.viewportData;
}
public getViewportIndex(): number {
return this.viewportIndex;
}
public getElement(): HTMLDivElement {
return this.element;
@@ -187,13 +179,15 @@ class ViewportInfo {
public setPublicDisplaySetOptions(
publicDisplaySetOptions: PublicDisplaySetOptions[] | DisplaySetSelector[]
): void {
): Array<DisplaySetOptions> {
// map the displaySetOptions and check if they are undefined then set them to default values
const displaySetOptions = this.mapDisplaySetOptions(
publicDisplaySetOptions
);
this.setDisplaySetOptions(displaySetOptions);
return this.displaySetOptions
}
public hasDisplaySet(displaySetInstanceUID: string): boolean {
@@ -217,7 +211,7 @@ class ViewportInfo {
public setPublicViewportOptions(
viewportOptionsEntry: PublicViewportOptions
): void {
): ViewportOptions {
let viewportType = viewportOptionsEntry.viewportType;
const {
toolGroupId = DEFAULT_TOOLGROUP_ID,
@@ -250,6 +244,8 @@ class ViewportInfo {
toolGroupId,
presentationIds,
});
return this.viewportOptions;
}
public setViewportOptions(viewportOptions: ViewportOptions): void {