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

@@ -74,13 +74,14 @@ const match = (
errorMessages = ['Something went wrong during validation.', e];
}
console.log(
'Test',
`${from}.${attribute}`,
readValues[attribute],
JSON.stringify(rule.constraint),
!errorMessages
);
// TODO: move to a logger
// console.log(
// 'Test',
// `${from}.${attribute}`,
// readValues[attribute],
// JSON.stringify(rule.constraint),
// !errorMessages
// );
if (!errorMessages) {
// If no errorMessages were returned, then validation passed.
@@ -118,7 +118,7 @@ function checkHpsBestMatch(hps) {
hps.run({ studies: [studyMatch], displaySets: studyMatchDisplaySets });
const { viewportMatchDetails } = hps.getMatchDetails();
expect(viewportMatchDetails.size).toBe(1);
expect(viewportMatchDetails.get(0)).toMatchObject({
expect(viewportMatchDetails.get('ctAXIAL')).toMatchObject({
viewportOptions: {
viewportId: 'ctAXIAL',
viewportType: 'volume',
@@ -14,6 +14,7 @@ import {
} from './custom-attribute/isDisplaySetFromUrl';
import numberOfDisplaySetsWithImages from './custom-attribute/numberOfDisplaySetsWithImages';
import seriesDescriptionsFromDisplaySets from './custom-attribute/seriesDescriptionsFromDisplaySets';
import uuidv4 from '../../utils/uuidv4';
type Protocol = HangingProtocol.Protocol | HangingProtocol.ProtocolGenerator;
@@ -132,11 +133,11 @@ export default class HangingProtocolService extends PubSubService {
> = new Map();
/**
* An array that contains for each viewport (viewportIndex) specified in the
* An array that contains for each viewport (viewportId) specified in the
* hanging protocol, an object of the form
*/
viewportMatchDetails: Map<
number, // viewportIndex
string, // viewportId
HangingProtocol.ViewportMatchDetails
> = new Map();
@@ -185,7 +186,7 @@ export default class HangingProtocolService extends PubSubService {
stage: HangingProtocol.ProtocolStage;
stageIndex: number;
activeStudy?: StudyMetadata;
viewportMatchDetails: Map<number, HangingProtocol.ViewportMatchDetails>;
viewportMatchDetails: Map<string, HangingProtocol.ViewportMatchDetails>;
displaySetMatchDetails: Map<string, HangingProtocol.DisplaySetMatchDetails>;
activeImageLoadStrategyName: string;
} {
@@ -404,7 +405,7 @@ export default class HangingProtocolService extends PubSubService {
/**
* Returns true, if the hangingProtocol has a custom loading strategy for the images
* and its callback has been added to the HangingProtocolService
* @returns {boolean} true
* @returns A boolean indicating whether a custom image load strategy has been added or not.
*/
public hasCustomImageLoadStrategy(): boolean {
return (
@@ -415,10 +416,31 @@ export default class HangingProtocolService extends PubSubService {
);
}
/**
* Returns a boolean indicating whether a custom image load has been performed or not.
* A custom image load is performed when a custom image load strategy is used to load images.
* This method is used internally by the HangingProtocolService to determine whether to perform
* a custom image load or not.
*
* @returns A boolean indicating whether a custom image load has been performed or not.
*/
public getCustomImageLoadPerformed(): boolean {
return this.customImageLoadPerformed;
}
/**
* Returns a boolean indicating whether a custom image load should be performed or not.
* A custom image load should be performed if a custom image load strategy has been added to the HangingProtocolService
* and it has not been performed yet.
*
* @returns A boolean indicating whether a custom image load should be performed or not.
*/
public getShouldPerformCustomImageLoad(): boolean {
return (
this.hasCustomImageLoadStrategy() && !this.getCustomImageLoadPerformed()
);
}
/**
* Set the strategy callback for loading images to the HangingProtocolService
* @param {string} name strategy name
@@ -519,21 +541,28 @@ export default class HangingProtocolService extends PubSubService {
for (let i = 0; i < rows * columns; i++) {
stage.viewports.push({
viewportOptions: defaultViewportOptions,
viewportOptions: {
...defaultViewportOptions,
viewportId: uuidv4(),
},
displaySets: [],
});
}
} else {
// Clone each viewport to ensure independent objects
stage.viewports = stage.viewports.map(viewport => ({
...viewport,
viewportOptions: {
...(viewport.viewportOptions || defaultViewportOptions),
viewportId: viewport.viewportOptions?.viewportId || uuidv4(),
},
displaySets: viewport.displaySets || [],
}));
stage.viewports.forEach(viewport => {
viewport.viewportOptions =
viewport.viewportOptions || defaultViewportOptions;
if (!viewport.displaySets) {
viewport.displaySets = [];
} else {
viewport.displaySets.forEach(displaySet => {
displaySet.options = displaySet.options || {};
});
}
viewport.displaySets.forEach(displaySet => {
displaySet.options = displaySet.options || {};
});
});
}
});
@@ -558,7 +587,7 @@ export default class HangingProtocolService extends PubSubService {
};
}
getViewportsRequireUpdate(viewportIndex, displaySetInstanceUID) {
getViewportsRequireUpdate(viewportId, displaySetInstanceUID) {
const { displaySetService } = this._servicesManager.services;
const displaySet = displaySetService.getDisplaySetByUID(
displaySetInstanceUID
@@ -570,15 +599,22 @@ export default class HangingProtocolService extends PubSubService {
const protocol = this.protocol;
const protocolStage = protocol.stages[this.stageIndex];
const protocolViewports = protocolStage.viewports;
const protocolViewport = protocolViewports[viewportIndex];
const defaultReturn = [
{
viewportIndex,
viewportId,
displaySetInstanceUIDs: [newDisplaySetInstanceUID],
},
];
if (!protocolViewports) {
return defaultReturn;
}
const protocolViewport = protocolViewports.find(
pv => pv.viewportOptions.viewportId === viewportId
);
// if no viewport, then we can assume there is no predefined set of
// rules that should be applied to this viewport while matching
if (!protocolViewport) {
@@ -644,13 +680,13 @@ export default class HangingProtocolService extends PubSubService {
}
// if we reach here, it means that the displaySetInstanceUIDs to be dropped
// in the viewportIndex are valid, and we can proceed with the update. However
// for the desired viewportId are valid, and we can proceed with the update. However
// we need to check if the displaySets that the viewport were showing
// was also referenced by other viewports, and if so, we need to update those
// viewports as well
// check if displaySetSelectors are used by other viewports, and
// store the viewportIndex and displaySetInstanceUIDs that need to be updated
// store the viewportId and displaySetInstanceUIDs that need to be updated
const viewportsToUpdate = [];
protocolViewports.forEach((viewport, index) => {
const viewportNeedsUpdate = viewport.displaySets.some(
@@ -661,7 +697,7 @@ export default class HangingProtocolService extends PubSubService {
if (viewportNeedsUpdate) {
// Try to recompute the viewport options based on the current
// viewportIndex that needs update but from its old/original un-computed
// viewportId that needs update but from its old/original un-computed
// viewport & displaySet options
if (originalProtocolStage) {
const originalViewport = originalProtocolStage.viewports[index];
@@ -692,7 +728,7 @@ export default class HangingProtocolService extends PubSubService {
);
viewportsToUpdate.push({
viewportIndex: index,
viewportId: viewport.viewportOptions.viewportId,
displaySetInstanceUIDs,
viewportOptions: viewport.viewportOptions,
displaySetOptions,
@@ -799,7 +835,7 @@ export default class HangingProtocolService extends PubSubService {
* @param protocolId - name of the registered protocol to be set
* @param options - options to be passed to the protocol, this is either an array
* of the displaySetInstanceUIDs to be set on ALL VIEWPORTS OF THE PROTOCOL or an object
* that contains viewportIndex as the key and displaySetInstanceUIDs as the value
* that contains viewportId as the key and displaySetInstanceUIDs as the value
* for each viewport that needs to be set.
* @param errorCallback - callback to be called if there is an error
* during the protocol application
@@ -918,7 +954,7 @@ export default class HangingProtocolService extends PubSubService {
const stage = stages[i];
if (stage.id === stageId && stage.status !== 'disabled') {
return i;
}
}
}
return;
}
@@ -1098,7 +1134,6 @@ export default class HangingProtocolService extends PubSubService {
// matching applied
this.viewportMatchDetails = new Map();
this.displaySetMatchDetails = new Map();
this.customImageLoadPerformed = false;
// Retrieve the current stage
const stageModel = this._getCurrentStageModel();
@@ -1148,7 +1183,11 @@ export default class HangingProtocolService extends PubSubService {
displaySetMatchDetails: Map<string, HangingProtocol.DisplaySetMatchDetails>;
} {
let matchedViewports = 0;
stageModel.viewports.forEach((viewport, viewportIndex) => {
stageModel.viewports.forEach(viewport => {
// Todo: we should probably assign a random viewportId if not defined
// below, but it feels odd since viewportGrid should handle this kind
// of thing
const viewportId = viewport.viewportOptions.viewportId;
const matchDetails = this._matchViewport(
viewport,
options,
@@ -1169,7 +1208,7 @@ export default class HangingProtocolService extends PubSubService {
displaySetOptions: it,
}));
}
viewportMatchDetails.set(viewportIndex, matchDetails);
viewportMatchDetails.set(viewportId, matchDetails);
}
});
return {
@@ -1231,7 +1270,7 @@ export default class HangingProtocolService extends PubSubService {
for (const subMatch of match.matchingScores) {
if (subMatch.displaySetInstanceUID === displaySetUID) {
return;
}
}
}
throw new Error(
`Reused viewport details ${id} with ds ${displaySetUID} not valid`
@@ -1442,15 +1481,15 @@ export default class HangingProtocolService extends PubSubService {
);
const studyMatchDetails = this.protocolEngine.findMatch(
study,
studyMatchingRules,
{
studies: this.studies,
displaySets: studyDisplaySets,
allDisplaySets: this.displaySets,
displaySetMatchDetails: this.displaySetMatchDetails,
study,
studyMatchingRules,
{
studies: this.studies,
displaySets: studyDisplaySets,
allDisplaySets: this.displaySets,
displaySetMatchDetails: this.displaySetMatchDetails,
studyInstanceUIDsIndex,
}
}
);
// Prevent bestMatch from being updated if the matchDetails' required attribute check has failed
@@ -1459,10 +1498,10 @@ export default class HangingProtocolService extends PubSubService {
}
this.debug(
'study',
study.StudyInstanceUID,
'display sets #',
studyDisplaySets.length
'study',
study.StudyInstanceUID,
'display sets #',
studyDisplaySets.length
);
studyDisplaySets.forEach(displaySet => {
const {
@@ -1471,15 +1510,15 @@ export default class HangingProtocolService extends PubSubService {
displaySetInstanceUID,
} = displaySet;
const seriesMatchDetails = this.protocolEngine.findMatch(
displaySet,
seriesMatchingRules,
// Todo: why we have images here since the matching type does not have it
{
studies: this.studies,
instance: displaySet.images?.[0],
displaySetMatchDetails: this.displaySetMatchDetails,
displaySets: studyDisplaySets,
}
displaySet,
seriesMatchingRules,
// Todo: why we have images here since the matching type does not have it
{
studies: this.studies,
instance: displaySet.images?.[0],
displaySetMatchDetails: this.displaySetMatchDetails,
displaySets: studyDisplaySets,
}
);
// Prevent bestMatch from being updated if the matchDetails' required attribute check has failed
@@ -649,10 +649,7 @@ class MeasurementService extends PubSubService {
* merely causes it to fire the event with the isConsumed set to true.
*/
public jumpToMeasurement(
viewportIndex: number,
measurementUID: string
): void {
public jumpToMeasurement(viewportId: string, measurementUID: string): void {
const measurement = this.measurements.get(measurementUID);
if (!measurement) {
@@ -660,7 +657,7 @@ class MeasurementService extends PubSubService {
return;
}
const consumableEvent = this.createConsumableEvent({
viewportIndex,
viewportId,
measurement,
});
@@ -4,7 +4,7 @@
* @typedef {Object} ViewportDialogProps
* @property {ReactElement|HTMLElement} [content=null] Modal content.
* @property {Object} [contentProps=null] Modal content props.
* @property {boolean} [viewportIndex=false] Modal is dismissible via the esc key.
* @property {boolean} [viewportId=false] Modal is dismissible via the esc key.
*/
const name = 'uiViewportDialogService';
@@ -22,12 +22,12 @@ const serviceImplementation = {
};
/**
* Show a new UI viewport dialog on the specified viewportIndex;
* Show a new UI viewport dialog on the specified viewportId;
*
* @param {ViewportDialogProps} props { content, contentProps, viewportIndex }
* @param {ViewportDialogProps} props { content, contentProps, viewportId }
*/
function _show({
viewportIndex,
viewportId,
id,
type,
message,
@@ -36,7 +36,7 @@ function _show({
onOutsideClick,
}) {
return serviceImplementation._show({
viewportIndex,
viewportId,
id,
type,
message,
@@ -59,7 +59,7 @@ function _hide() {
* @param {*} {
* hide: hideImplementation,
* show: showImplementation,
* viewportIndex,
* viewportId,
* }
*/
function setServiceImplementation({
@@ -3,7 +3,7 @@ import { getPresentationIds, PresentationIds } from './getPresentationIds';
class ViewportGridService extends PubSubService {
public static readonly EVENTS = {
ACTIVE_VIEWPORT_INDEX_CHANGED: 'event::activeviewportindexchanged',
ACTIVE_VIEWPORT_ID_CHANGED: 'event::activeviewportidchanged',
LAYOUT_CHANGED: 'event::layoutChanged',
GRID_STATE_CHANGED: 'event::gridStateChanged',
};
@@ -27,7 +27,7 @@ class ViewportGridService extends PubSubService {
public setServiceImplementation({
getState: getStateImplementation,
setActiveViewportIndex: setActiveViewportIndexImplementation,
setActiveViewportId: setActiveViewportIdImplementation,
setDisplaySetsForViewports: setDisplaySetsForViewportsImplementation,
setLayout: setLayoutImplementation,
reset: resetImplementation,
@@ -38,9 +38,9 @@ class ViewportGridService extends PubSubService {
if (getStateImplementation) {
this.serviceImplementation._getState = getStateImplementation;
}
if (setActiveViewportIndexImplementation) {
this.serviceImplementation._setActiveViewportIndex =
setActiveViewportIndexImplementation;
if (setActiveViewportIdImplementation) {
this.serviceImplementation._setActiveViewport =
setActiveViewportIdImplementation;
}
if (setDisplaySetsForViewportsImplementation) {
this.serviceImplementation._setDisplaySetsForViewports =
@@ -64,13 +64,11 @@ class ViewportGridService extends PubSubService {
}
}
public setActiveViewportIndex(index) {
this.serviceImplementation._setActiveViewportIndex(index);
public setActiveViewportId(id: string) {
this.serviceImplementation._setActiveViewport(id);
const state = this.getState();
const viewportId = state.viewports[index]?.viewportOptions?.viewportId;
this._broadcastEvent(this.EVENTS.ACTIVE_VIEWPORT_INDEX_CHANGED, {
viewportIndex: index,
viewportId,
this._broadcastEvent(this.EVENTS.ACTIVE_VIEWPORT_ID_CHANGED, {
viewportId: id,
});
}
@@ -89,7 +87,7 @@ class ViewportGridService extends PubSubService {
const viewports = [];
for (const viewport of props) {
const updatedViewport = state.viewports[viewport.viewportIndex];
const updatedViewport = state.viewports.get(viewport.viewportId);
if (updatedViewport) {
viewports.push(updatedViewport);
} else {
@@ -118,7 +116,7 @@ class ViewportGridService extends PubSubService {
numRows,
layoutOptions,
layoutType = 'grid',
activeViewportIndex = undefined,
activeViewportId = undefined,
findOrCreateViewport = undefined,
}) {
this.serviceImplementation._setLayout({
@@ -126,7 +124,7 @@ class ViewportGridService extends PubSubService {
numRows,
layoutOptions,
layoutType,
activeViewportIndex,
activeViewportId,
findOrCreateViewport,
});
this._broadcastEvent(this.EVENTS.LAYOUT_CHANGED, {
@@ -160,8 +158,8 @@ class ViewportGridService extends PubSubService {
return this.serviceImplementation._getNumViewportPanes();
}
public getLayoutOptionsFromState(state) {
return state.viewports.map(viewport => {
public getLayoutOptionsFromState(state: any): { x: number; y: number; width: number; height: number }[] {
return Array.from(state.viewports.entries()).map(([_, viewport]) => {
return {
x: viewport.x,
y: viewport.y,
@@ -25,7 +25,7 @@ const addUniqueIndex = (arr, key, viewports, isUpdatingSameViewport) => {
arr[arr.length - 1] = displayInstance;
const testId = arr.join(JOIN_STR);
if (
!viewports.find(
!Array.from(viewports.values()).find(
viewport => viewport.viewportOptions?.presentationIds?.[key] === testId
)
) {
@@ -126,11 +126,11 @@ const getPresentationIds = (viewport, viewports): PresentationIds => {
}
// only add unique index if the viewport is getting inserted and not updated
const isUpdatingSameViewport = viewports.some(v => {
const isUpdatingSameViewport = Array.from(viewports.values()).some(v => {
return (
v.displaySetInstanceUIDs.toString() ===
viewport.displaySetInstanceUIDs.toString() &&
v.viewportIndex === viewport.viewportIndex
v.viewportId === viewport.viewportId
);
});
+3 -1
View File
@@ -61,7 +61,7 @@ export type SetProtocolOptions = {
export type HangingProtocolMatchDetails = {
displaySetMatchDetails: Map<string, DisplaySetMatchDetails>;
viewportMatchDetails: Map<number, ViewportMatchDetails>;
viewportMatchDetails: Map<string, ViewportMatchDetails>;
};
export type ConstraintValue =
@@ -263,6 +263,8 @@ export type ProtocolNotifications = {
export type Protocol = {
// Mandatory
id: string;
/** A description of this protocol. Used as a tool tip for the user. */
description?: string;
/** Maps ids to display set selectors to choose display sets */
displaySetSelectors: Record<string, DisplaySetSelector>;
/** A default viewport to use for any stage to select new viewport layouts. */
+2
View File
@@ -1,6 +1,7 @@
import ObjectPath from './objectPath';
import absoluteUrl from './absoluteUrl';
import guid from './guid';
import uuidv4 from './uuidv4';
import sortBy from './sortBy.js';
import writeScript from './writeScript.js';
import b64toBlob from './b64toBlob.js';
@@ -41,6 +42,7 @@ import { splitComma, getSplitParam } from './splitComma';
const utils = {
guid,
uuidv4,
ObjectPath,
absoluteUrl,
sortBy,
+1
View File
@@ -39,6 +39,7 @@ describe('Top level exports', () => {
'hierarchicalListUtils',
'progressTrackingUtils',
'subscribeToNextViewportGridChange',
'uuidv4',
].sort();
const exports = Object.keys(utils.default).sort();
@@ -1,7 +1,7 @@
import { ViewportGridService } from '../services';
/**
* Subscribes to the very next LAYOUT_CHANGED or GRID_STATE_CHANGED event that
* Subscribes to the very next LAYOUT_CHANGED event that
* is not currently on the event queue. The subscriptions are made on a 'zero'
* timeout so as to avoid responding to any of those events currently on the event queue.
* The subscription persists only for a single invocation of either event.
@@ -24,10 +24,6 @@ function subscribeToNextViewportGridChange(
viewportGridService.EVENTS.LAYOUT_CHANGED,
callback
),
viewportGridService.subscribe(
viewportGridService.EVENTS.GRID_STATE_CHANGED,
callback
),
];
};