diff --git a/platform/core/src/services/HangingProtocolService/HangingProtocolService.ts b/platform/core/src/services/HangingProtocolService/HangingProtocolService.ts index 37dacef218..e69650bda8 100644 --- a/platform/core/src/services/HangingProtocolService/HangingProtocolService.ts +++ b/platform/core/src/services/HangingProtocolService/HangingProtocolService.ts @@ -382,12 +382,30 @@ export default class HangingProtocolService extends PubSubService { return; } - const matchedProtocol = this.protocolEngine.run({ + let matchedProtocol = this.protocolEngine.run({ studies: this.studies, activeStudy, displaySets, }); - this._setProtocol(matchedProtocol); + let nthBestMatch = 1; + const matchedProtocolSize = this.protocolEngine.getMatchedProtocolsSize(); + let done = false; + while (!done && nthBestMatch <= matchedProtocolSize) { + try { + this._setProtocol(matchedProtocol); + done = true; + } catch (e) { + // something went wrong while setting current matchedProtocol, so we rollback and + // try again with next nth best matched protocol + nthBestMatch++; + matchedProtocol = this.protocolEngine.run({ + studies: this.studies, + activeStudy, + displaySets, + nthBest: nthBestMatch + }); + } + } } /** @@ -856,26 +874,39 @@ export default class HangingProtocolService extends PubSubService { options = null as HangingProtocol.SetProtocolOptions ) { const stages = this.protocol.stages; + let failureCounter = 0; for (let i = 0; i < stages.length; i++) { - const stage = stages[i]; + try { + const stage = stages[i]; - const { matchedViewports } = this._matchAllViewports( - stage, - options, - new Map() - ); - const activation = stage.stageActivation || {}; - if (this.matchActivation(matchedViewports, activation.passive, 0)) { - if (this.matchActivation(matchedViewports, activation.enabled, 1)) { - stage.status = 'enabled'; + const { matchedViewports } = this._matchAllViewports( + stage, + options, + new Map() + ) || {}; + const activation = stage.stageActivation || {}; + + if (this.matchActivation(matchedViewports, activation.passive, 0)) { + if (this.matchActivation(matchedViewports, activation.enabled, 1)) { + stage.status = 'enabled'; + } else { + stage.status = 'passive'; + } } else { - stage.status = 'passive'; + stage.status = 'disabled'; } - } else { - stage.status = 'disabled'; + } catch (e) { + stages[i].status = 'passive'; + failureCounter++; + console.warn(`The hanging protocol viewport is requesting to display the matching displaysets for ${stages[i]} but something went wrong and it could not be matched.`); } } + // it means there is no valid stage + if (failureCounter === stages.length) { + throw new Error(); + } + this._broadcastEvent(this.EVENTS.STAGE_ACTIVATION, { protocol: this.protocol, stages: this.protocol.stages, @@ -1284,6 +1315,14 @@ export default class HangingProtocolService extends PubSubService { ); } }); + + // there is no displayset found for given protocol + if (!displaySetsInfo.length) { + throw new Error( + `Can't find a displaySet match for any viewport` + ); + } + return { viewportOptions, displaySetsInfo, diff --git a/platform/core/src/services/HangingProtocolService/ProtocolEngine.js b/platform/core/src/services/HangingProtocolService/ProtocolEngine.js index b3c18ab40c..5db9ba3071 100644 --- a/platform/core/src/services/HangingProtocolService/ProtocolEngine.js +++ b/platform/core/src/services/HangingProtocolService/ProtocolEngine.js @@ -14,12 +14,13 @@ export default class ProtocolEngine { * @param props.studies is a list of studies to compare against (for priors evaluation) * @param props.activeStudy is the current metadata for the study to display. * @param props.displaySets are the list of display sets which can be modified. + * @param props.nthBest is the nth best match from the existing options. In case of undefined it consider the first best. */ - run({ studies, displaySets, activeStudy }) { + run({ studies, displaySets, activeStudy, nthBest }) { this.studies = studies; this.study = activeStudy || studies[0]; this.displaySets = displaySets; - return this.getBestProtocolMatch(); + return this.getBestProtocolMatch(nthBest); } // /** @@ -33,20 +34,32 @@ export default class ProtocolEngine { /** * Return the best matched Protocol to the current study or set of studies + * @param nthBest is the nth best match from the existing options. In case of undefined it consider the first best. * @returns {*} */ - getBestProtocolMatch() { - // Run the matching to populate matchedProtocols Set and Map - this.updateProtocolMatches(); + getBestProtocolMatch(nthBest) { + + if(!nthBest) { + // Run the matching to populate matchedProtocols Set and Map + this.updateProtocolMatches(); + } // Retrieve the highest scoring Protocol - const bestMatch = this._getHighestScoringProtocol(); + const bestMatch = this._getHighestScoringProtocol(nthBest); console.log('ProtocolEngine::getBestProtocolMatch bestMatch', bestMatch); return bestMatch; } + /** + * Returns the amount of matched protocols + * + * @returns number + */ + getMatchedProtocolsSize() { + return this.matchedProtocols.size; + } /** * Populates the MatchedProtocols Collection by running the matching procedure */ @@ -166,16 +179,25 @@ export default class ProtocolEngine { this.matchedProtocolScores = {}; } - _largestKeyByValue(obj) { - return Object.keys(obj).reduce((a, b) => (obj[a] > obj[b] ? a : b)); + _largestKeyByValue(obj, nth = 1) { + const sortedObj = Object.keys(obj).sort((a, b) => (obj[a] > obj[b])); + + return sortedObj[nth - 1]; } - _getHighestScoringProtocol() { + /** + * Get the protocol with highest score. + * If nth is present return the nth best match. + * @param nth tells that we need the nth highest scoring protocol. Default if 1 (first). + * + */ + _getHighestScoringProtocol(nth) { if (!Object.keys(this.matchedProtocolScores).length) { return; } const highestScoringProtocolId = this._largestKeyByValue( - this.matchedProtocolScores + this.matchedProtocolScores, + nth ); return this.matchedProtocols.get(highestScoringProtocolId); }