Moving updates on Hanging Protocols from Nucleus to private OHIF branch
This commit is contained in:
parent
2a6cb42228
commit
744a0bd76e
@ -50,6 +50,10 @@ HP.Protocol = class Protocol {
|
|||||||
// default protocols.
|
// default protocols.
|
||||||
this.locked = false;
|
this.locked = false;
|
||||||
|
|
||||||
|
// Boolean value to indicate if the protocol has updated priors information
|
||||||
|
// it's set in "updateNumberOfPriorsReferenced" function
|
||||||
|
this.hasUpdatedPriorsInformation = false;
|
||||||
|
|
||||||
// Apply the desired name
|
// Apply the desired name
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
|
||||||
@ -73,28 +77,42 @@ HP.Protocol = class Protocol {
|
|||||||
// and Stages
|
// and Stages
|
||||||
this.protocolMatchingRules = [];
|
this.protocolMatchingRules = [];
|
||||||
this.stages = [];
|
this.stages = [];
|
||||||
|
|
||||||
|
// Define auxiliary values for priors
|
||||||
this.numberOfPriorsReferenced = 0;
|
this.numberOfPriorsReferenced = 0;
|
||||||
|
this.numberOfPriorsReferencedRequired = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateNumberOfPriorsReferenced() {
|
updateNumberOfPriorsReferenced() {
|
||||||
var numPriorsReferenced = 0;
|
let numPriorsReferenced = 0;
|
||||||
|
let numberOfPriorsReferencedRequired = 0;
|
||||||
|
|
||||||
this.stages.forEach(function(stage) {
|
this.stages.forEach(stage => {
|
||||||
if (!stage.viewports) {
|
if (!stage.viewports) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
stage.viewports.forEach(function(viewport) {
|
stage.viewports.forEach(viewport => {
|
||||||
if (!viewport.studyMatchingRules) {
|
if (!viewport.studyMatchingRules) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
viewport.studyMatchingRules.forEach(function(rule) {
|
viewport.studyMatchingRules.forEach(rule => {
|
||||||
if (rule.attribute === 'abstractPriorValue') {
|
if (rule.attribute === 'abstractPriorValue') {
|
||||||
|
// If the rule is required
|
||||||
|
if (rule.required) {
|
||||||
|
numberOfPriorsReferencedRequired++;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Double check here that the abstractPriorValue is not
|
// TODO: Double check here that the abstractPriorValue is not
|
||||||
// set as zero
|
// set as zero
|
||||||
numPriorsReferenced++;
|
numPriorsReferenced++;
|
||||||
} else if (rule.attribute === 'relativeTime') {
|
} else if (rule.attribute === 'relativeTime') {
|
||||||
|
// If the rule is required
|
||||||
|
if (rule.required) {
|
||||||
|
numberOfPriorsReferencedRequired++;
|
||||||
|
}
|
||||||
|
|
||||||
numPriorsReferenced++;
|
numPriorsReferenced++;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -102,6 +120,10 @@ HP.Protocol = class Protocol {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.numberOfPriorsReferenced = numPriorsReferenced;
|
this.numberOfPriorsReferenced = numPriorsReferenced;
|
||||||
|
this.numberOfPriorsReferencedRequired = numberOfPriorsReferencedRequired;
|
||||||
|
|
||||||
|
// To indicate that the priors informations were updated
|
||||||
|
this.hasUpdatedPriorsInformation = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -115,6 +137,11 @@ HP.Protocol = class Protocol {
|
|||||||
this.modifiedBy = Meteor.userId;
|
this.modifiedBy = Meteor.userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Protocol has been modified, so mark priors information
|
||||||
|
// as "outdated"
|
||||||
|
this.hasUpdatedPriorsInformation = false;
|
||||||
|
|
||||||
|
// Update number of priors referenced info
|
||||||
this.updateNumberOfPriorsReferenced();
|
this.updateNumberOfPriorsReferenced();
|
||||||
|
|
||||||
// Update the modifiedDate with the current Date/Time
|
// Update the modifiedDate with the current Date/Time
|
||||||
|
|||||||
@ -0,0 +1,28 @@
|
|||||||
|
// Define an empty object to store callbacks that are used to retrieve custom attributes
|
||||||
|
// The simplest example for a custom attribute is the Timepoint type (i.e. baseline or follow-up)
|
||||||
|
// used in the LesionTracker application.
|
||||||
|
//
|
||||||
|
// Timepoint type can be obtained given a studyId, and this is done through a custom callback.
|
||||||
|
// Developers can define attributes (i.e. attributeId = timepointType) with a name ('Timepoint Type')
|
||||||
|
// and a callback function that is used to calculate them.
|
||||||
|
//
|
||||||
|
// The input to the callback, which is called during viewport-image matching rule evaluation
|
||||||
|
// is the set of attributes that contains the specified attribute. In our example, timepointType is
|
||||||
|
// linked to the study attributes, and so the inputs to the callback is an object containing
|
||||||
|
// the study attributes.
|
||||||
|
HP.CustomAttributeRetrievalCallbacks = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a custom attribute to be used in the HangingProtocol UI and matching rules, including a
|
||||||
|
* callback that will be used to calculate the attribute value.
|
||||||
|
*
|
||||||
|
* @param attributeId The ID used to refer to the attribute (e.g. 'timepointType')
|
||||||
|
* @param attributeName The name of the attribute to be displayed (e.g. 'Timepoint Type')
|
||||||
|
* @param callback The function used to calculate the attribute value from the other attributes at its level (e.g. study/series/image)
|
||||||
|
*/
|
||||||
|
HP.addCustomAttribute = (attributeId, attributeName, callback) => {
|
||||||
|
HP.CustomAttributeRetrievalCallbacks[attributeId] = {
|
||||||
|
name: attributeName,
|
||||||
|
callback: callback
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
// Define an empty object to store callbacks that are used to apply custom viewport settings
|
||||||
|
// after a viewport is rendered.
|
||||||
|
HP.CustomViewportSettings = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a custom setting that can be chosen in the HangingProtocol UI and applied to a Viewport
|
||||||
|
*
|
||||||
|
* @param settingId The ID used to refer to the setting (e.g. 'displayCADMarkers')
|
||||||
|
* @param settingName The name of the setting to be displayed (e.g. 'Display CAD Markers')
|
||||||
|
* @param options
|
||||||
|
* @param callback A function to be run after a viewport is rendered with a series
|
||||||
|
*/
|
||||||
|
HP.addCustomViewportSetting = (settingId, settingName, options, callback) => {
|
||||||
|
HP.CustomViewportSettings[settingId] = {
|
||||||
|
id: settingId,
|
||||||
|
text: settingName,
|
||||||
|
options: options,
|
||||||
|
callback: callback
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
// Sorts an array by score
|
||||||
|
const sortByScore = arr => {
|
||||||
|
arr.sort((a, b) => {
|
||||||
|
return b.score - a.score;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export { sortByScore };
|
||||||
113
Packages/ohif-hanging-protocols/client/matcher/HPMatcher.js
Normal file
113
Packages/ohif-hanging-protocols/client/matcher/HPMatcher.js
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
// OHIF Modules
|
||||||
|
import { Viewerbase } from 'meteor/ohif:viewerbase';
|
||||||
|
|
||||||
|
// Local imports
|
||||||
|
import '../customAttributes';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import Constants
|
||||||
|
*/
|
||||||
|
const { OHIFError, metadata: { StudySummary, InstanceMetadata } } = Viewerbase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Match a Metadata instance against rules using Validate.js for validation.
|
||||||
|
* @param {StudySummary|InstanceMetadata} metadataInstance Metadata instance object
|
||||||
|
* @param {Array} rules Array of MatchingRules instances (StudyMatchingRule|SeriesMatchingRule|ImageMatchingRule) for the match
|
||||||
|
* @return {Object} Matching Object with score and details (which rule passed or failed)
|
||||||
|
*/
|
||||||
|
const match = (metadataInstance, rules) => {
|
||||||
|
|
||||||
|
// Make sure the supplied data is valid.
|
||||||
|
if (!(metadataInstance instanceof StudySummary || metadataInstance instanceof InstanceMetadata)) {
|
||||||
|
throw new OHIFError('HPMatcher::match metadataInstance must be an instance of StudySummary or InstanceMetadata');
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
format: 'grouped'
|
||||||
|
};
|
||||||
|
|
||||||
|
const details = {
|
||||||
|
passed: [],
|
||||||
|
failed: []
|
||||||
|
};
|
||||||
|
|
||||||
|
let requiredFailed = false;
|
||||||
|
let score = 0;
|
||||||
|
|
||||||
|
rules.forEach(rule => {
|
||||||
|
const attribute = rule.attribute;
|
||||||
|
let customAttributeExists = metadataInstance.customAttributeExists(attribute);
|
||||||
|
|
||||||
|
// If the metadataInstance we are testing (e.g. study, series, or instance MetadataInstance) do
|
||||||
|
// not contain the attribute specified in the rule, check whether or not they have been
|
||||||
|
// defined in the CustomAttributeRetrievalCallbacks Object.
|
||||||
|
if (!customAttributeExists && HP.CustomAttributeRetrievalCallbacks.hasOwnProperty(attribute)) {
|
||||||
|
const customAttribute = HP.CustomAttributeRetrievalCallbacks[attribute];
|
||||||
|
metadataInstance.setCustomAttribute(attribute, customAttribute.callback(metadataInstance));
|
||||||
|
customAttributeExists = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format the constraint as required by Validate.js
|
||||||
|
const testConstraint = {
|
||||||
|
[attribute]: rule.constraint
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a single attribute object to be validated, since metadataInstance is an
|
||||||
|
// instance of Metadata (StudyMetadata, SeriesMetadata or InstanceMetadata)
|
||||||
|
const attributeValue = customAttributeExists ? metadataInstance.getCustomAttribute(attribute) : metadataInstance.getTagValue(attribute);
|
||||||
|
const attributeMap = {
|
||||||
|
[attribute]: attributeValue
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use Validate.js to evaluate the constraints on the specified metadataInstance
|
||||||
|
let errorMessages;
|
||||||
|
try {
|
||||||
|
errorMessages = validate(attributeMap, testConstraint, [options]);
|
||||||
|
} catch (e) {
|
||||||
|
errorMessages = [ 'Something went wrong during validation.', e ];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!errorMessages) {
|
||||||
|
// If no errorMessages were returned, then validation passed.
|
||||||
|
|
||||||
|
// Add the rule's weight to the total score
|
||||||
|
score += rule.weight;
|
||||||
|
|
||||||
|
// Log that this rule passed in the matching details object
|
||||||
|
details.passed.push({
|
||||||
|
rule
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// If errorMessages were present, then validation failed
|
||||||
|
|
||||||
|
// If the rule that failed validation was Required, then
|
||||||
|
// mark that a required Rule has failed
|
||||||
|
if (rule.required) {
|
||||||
|
requiredFailed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log that this rule failed in the matching details object
|
||||||
|
// and include any error messages
|
||||||
|
details.failed.push({
|
||||||
|
rule,
|
||||||
|
errorMessages
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// If a required Rule has failed Validation, set the matching score to zero
|
||||||
|
if (requiredFailed) {
|
||||||
|
score = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
score,
|
||||||
|
details
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const HPMatcher = {
|
||||||
|
match
|
||||||
|
};
|
||||||
|
|
||||||
|
export { HPMatcher };
|
||||||
@ -1,17 +1,24 @@
|
|||||||
import { Meteor } from 'meteor/meteor';
|
import { Meteor } from 'meteor/meteor';
|
||||||
import { _ } from 'meteor/underscore';
|
import { _ } from 'meteor/underscore';
|
||||||
|
import { Session } from 'meteor/session';
|
||||||
|
|
||||||
// OHIF Modules
|
// OHIF Modules
|
||||||
import { OHIF } from 'meteor/ohif:core';
|
import { OHIF } from 'meteor/ohif:core';
|
||||||
import 'meteor/ohif:viewerbase';
|
import 'meteor/ohif:viewerbase';
|
||||||
|
|
||||||
|
// Hanging Protocol local imports
|
||||||
|
import { HPMatcher } from './matcher/HPMatcher';
|
||||||
|
import { sortByScore } from './lib/sortByScore';
|
||||||
|
import './customViewportSettings';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Import Constants
|
* Import Constants
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { OHIFError } = OHIF.viewerbase;
|
const { OHIFError, metadata: { StudyMetadata, SeriesMetadata, InstanceMetadata, StudySummary } } = OHIF.viewerbase;
|
||||||
const { StudyMetadata, SeriesMetadata, InstanceMetadata, StudySummary } = OHIF.viewerbase.metadata;
|
|
||||||
|
|
||||||
|
// Useful constants
|
||||||
|
const ABSTRACT_PRIOR_VALUE = 'abstractPriorValue';
|
||||||
|
|
||||||
// Define a global variable that will be used to refer to the Protocol Engine
|
// Define a global variable that will be used to refer to the Protocol Engine
|
||||||
// It must be populated by HP.setEngine when the Viewer is initialized and a ProtocolEngine
|
// It must be populated by HP.setEngine when the Viewer is initialized and a ProtocolEngine
|
||||||
@ -29,172 +36,18 @@ ProtocolEngine = undefined;
|
|||||||
* @param protocolEngine An instantiated ProtocolEngine linked to a LayoutManager from the
|
* @param protocolEngine An instantiated ProtocolEngine linked to a LayoutManager from the
|
||||||
* Viewerbase package
|
* Viewerbase package
|
||||||
*/
|
*/
|
||||||
HP.setEngine = function(protocolEngine) {
|
HP.setEngine = protocolEngine => {
|
||||||
ProtocolEngine = protocolEngine;
|
ProtocolEngine = protocolEngine;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Define an empty object to store callbacks that are used to retrieve custom attributes
|
Meteor.startup(() => {
|
||||||
// The simplest example for a custom attribute is the Timepoint type (i.e. baseline or follow-up)
|
HP.addCustomViewportSetting('wlPreset', 'Window/Level Preset', Object.create(null), (element, optionValue) => {
|
||||||
// used in the LesionTracker application.
|
|
||||||
//
|
|
||||||
// Timepoint type can be obtained given a studyId, and this is done through a custom callback.
|
|
||||||
// Developers can define attributes (i.e. attributeId = timepointType) with a name ('Timepoint Type')
|
|
||||||
// and a callback function that is used to calculate them.
|
|
||||||
//
|
|
||||||
// The input to the callback, which is called during viewport-image matching rule evaluation
|
|
||||||
// is the set of attributes that contains the specified attribute. In our example, timepointType is
|
|
||||||
// linked to the study attributes, and so the inputs to the callback is an object containing
|
|
||||||
// the study attributes.
|
|
||||||
HP.CustomAttributeRetrievalCallbacks = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a custom attribute to be used in the HangingProtocol UI and matching rules, including a
|
|
||||||
* callback that will be used to calculate the attribute value.
|
|
||||||
*
|
|
||||||
* @param attributeId The ID used to refer to the attribute (e.g. 'timepointType')
|
|
||||||
* @param attributeName The name of the attribute to be displayed (e.g. 'Timepoint Type')
|
|
||||||
* @param callback The function used to calculate the attribute value from the other attributes at its level (e.g. study/series/image)
|
|
||||||
*/
|
|
||||||
HP.addCustomAttribute = function(attributeId, attributeName, callback) {
|
|
||||||
HP.CustomAttributeRetrievalCallbacks[attributeId] = {
|
|
||||||
name: attributeName,
|
|
||||||
callback: callback
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// Define an empty object to store callbacks that are used to apply custom viewport settings
|
|
||||||
// after a viewport is rendered.
|
|
||||||
HP.CustomViewportSettings = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a custom setting that can be chosen in the HangingProtocol UI and applied to a Viewport
|
|
||||||
*
|
|
||||||
* @param settingId The ID used to refer to the setting (e.g. 'displayCADMarkers')
|
|
||||||
* @param settingName The name of the setting to be displayed (e.g. 'Display CAD Markers')
|
|
||||||
* @param options
|
|
||||||
* @param callback A function to be run after a viewport is rendered with a series
|
|
||||||
*/
|
|
||||||
HP.addCustomViewportSetting = function(settingId, settingName, options, callback) {
|
|
||||||
HP.CustomViewportSettings[settingId] = {
|
|
||||||
id: settingId,
|
|
||||||
text: settingName,
|
|
||||||
options: options,
|
|
||||||
callback: callback
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
Meteor.startup(function() {
|
|
||||||
HP.addCustomViewportSetting('wlPreset', 'Window/Level Preset', Object.create(null), function(element, optionValue) {
|
|
||||||
if (optionValue in OHIF.viewer.wlPresets) {
|
if (optionValue in OHIF.viewer.wlPresets) {
|
||||||
OHIF.viewerbase.wlPresets.applyWLPreset(optionValue, element);
|
OHIF.viewerbase.wlPresets.applyWLPreset(optionValue, element);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Match a Metadata instance against rules using Validate.js for validation.
|
|
||||||
* @param {StudySummary|InstanceMetadata} metadataInstance Metadata instance object
|
|
||||||
* @param {Array} rules Array of MatchingRules instances (StudyMatchingRule|SeriesMatchingRule|ImageMatchingRule) for the match
|
|
||||||
* @return {Object} Matching Object with score and details (which rule passed or failed)
|
|
||||||
*/
|
|
||||||
HP.match = function(metadataInstance, rules) {
|
|
||||||
|
|
||||||
// Make sure the supplied data is valid.
|
|
||||||
if (!(metadataInstance instanceof StudySummary || metadataInstance instanceof InstanceMetadata)) {
|
|
||||||
throw new OHIFError('HP::match metadataInstance must be an instance of StudySummary or InstanceMetadata');
|
|
||||||
}
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
format: 'grouped'
|
|
||||||
};
|
|
||||||
|
|
||||||
const details = {
|
|
||||||
passed: [],
|
|
||||||
failed: []
|
|
||||||
};
|
|
||||||
|
|
||||||
let requiredFailed = false;
|
|
||||||
let score = 0;
|
|
||||||
|
|
||||||
rules.forEach(rule => {
|
|
||||||
const attribute = rule.attribute;
|
|
||||||
let customAttributeExists = metadataInstance.customAttributeExists(attribute);
|
|
||||||
|
|
||||||
// If the metadataInstance we are testing (e.g. study, series, or instance MetadataInstance) do
|
|
||||||
// not contain the attribute specified in the rule, check whether or not they have been
|
|
||||||
// defined in the CustomAttributeRetrievalCallbacks Object.
|
|
||||||
if (!customAttributeExists && HP.CustomAttributeRetrievalCallbacks.hasOwnProperty(attribute)) {
|
|
||||||
const customAttribute = HP.CustomAttributeRetrievalCallbacks[attribute];
|
|
||||||
metadataInstance.setCustomAttribute(attribute, customAttribute.callback(metadataInstance));
|
|
||||||
customAttributeExists = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format the constraint as required by Validate.js
|
|
||||||
const testConstraint = {
|
|
||||||
[attribute]: rule.constraint
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create a single attribute object to be validated, since metadataInstance is an
|
|
||||||
// instance of Metadata (StudyMetadata, SeriesMetadata or InstanceMetadata)
|
|
||||||
const attributeValue = customAttributeExists ? metadataInstance.getCustomAttribute(attribute) : metadataInstance.getTagValue(attribute);
|
|
||||||
const attributeMap = {
|
|
||||||
[attribute]: attributeValue
|
|
||||||
};
|
|
||||||
|
|
||||||
// Use Validate.js to evaluate the constraints on the specified metadataInstance
|
|
||||||
let errorMessages;
|
|
||||||
try {
|
|
||||||
errorMessages = validate(attributeMap, testConstraint, [options]);
|
|
||||||
} catch (e) {
|
|
||||||
errorMessages = [ 'Something went wrong during validation.', e ];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!errorMessages) {
|
|
||||||
// If no errorMessages were returned, then validation passed.
|
|
||||||
|
|
||||||
// Add the rule's weight to the total score
|
|
||||||
score += rule.weight;
|
|
||||||
|
|
||||||
// Log that this rule passed in the matching details object
|
|
||||||
details.passed.push({
|
|
||||||
rule
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// If errorMessages were present, then validation failed
|
|
||||||
|
|
||||||
// If the rule that failed validation was Required, then
|
|
||||||
// mark that a required Rule has failed
|
|
||||||
if (rule.required) {
|
|
||||||
requiredFailed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log that this rule failed in the matching details object
|
|
||||||
// and include any error messages
|
|
||||||
details.failed.push({
|
|
||||||
rule,
|
|
||||||
errorMessages
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// If a required Rule has failed Validation, set the matching score to zero
|
|
||||||
if (requiredFailed) {
|
|
||||||
score = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
score,
|
|
||||||
details
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const sortByScore = arr => {
|
|
||||||
arr.sort((a, b) => {
|
|
||||||
return b.score - a.score;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
HP.ProtocolEngine = class ProtocolEngine {
|
HP.ProtocolEngine = class ProtocolEngine {
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
@ -241,7 +94,8 @@ HP.ProtocolEngine = class ProtocolEngine {
|
|||||||
* Resets the ProtocolEngine to the best match
|
* Resets the ProtocolEngine to the best match
|
||||||
*/
|
*/
|
||||||
reset() {
|
reset() {
|
||||||
const protocol = this.getBestMatch();
|
const protocol = this.getBestProtocolMatch();
|
||||||
|
|
||||||
this.setHangingProtocol(protocol);
|
this.setHangingProtocol(protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,51 +108,112 @@ HP.ProtocolEngine = class ProtocolEngine {
|
|||||||
return this.protocol.stages[this.stage];
|
return this.protocol.stages[this.stage];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get number of priors rules for a given protocol. This rules are for raising up the score when
|
||||||
|
* matching a protocol against study's number of priors. Protocols with same study/series/instance
|
||||||
|
* rules that references the exactly number of priors that a study will have highest score.
|
||||||
|
* @param {Integer} numberOfPriorsReferenced Number of priors required by the protocol
|
||||||
|
* @return {Array} Returs an array of HP.ProtocolMatchingRule objects
|
||||||
|
*/
|
||||||
|
getNumberOfPriorsRules(numberOfPriorsReferenced) {
|
||||||
|
const rules = [];
|
||||||
|
|
||||||
|
// If study at least the number of priors required by the protocol
|
||||||
|
const requiredPriorsRule = new HP.ProtocolMatchingRule('numberOfPriorsReferenced', {
|
||||||
|
numericality: {
|
||||||
|
greaterThanOrEqualTo: numberOfPriorsReferenced
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// If study has the same number of priors
|
||||||
|
const equalPriorsRule = new HP.ProtocolMatchingRule('numberOfPriorsReferenced', {
|
||||||
|
numericality: {
|
||||||
|
equalTo: numberOfPriorsReferenced
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
rules.push(requiredPriorsRule, equalPriorsRule);
|
||||||
|
|
||||||
|
return rules;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a given protocol requires a minimum number of priors.
|
||||||
|
* @param {HP.Protocol} protocol HP.Protocol object that contains rules
|
||||||
|
* @return {Boolean} Returns true if the protocol requires priors or false otherwise
|
||||||
|
*/
|
||||||
|
protocolRequiresPriors(protocol) {
|
||||||
|
return protocol.numberOfPriorsReferencedRequired > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the best protocols from Protocol Store, matching each protocol matching rules
|
||||||
|
* with the given study. The best protocol are orded by score and returned in an array
|
||||||
|
* @param {Object} study StudyMetadata instance object
|
||||||
|
* @return {Array} Array of match objects or an empty array if no match was found
|
||||||
|
* Each match object has the score of the matching and the matched
|
||||||
|
* protocol
|
||||||
|
*/
|
||||||
findMatchByStudy(study) {
|
findMatchByStudy(study) {
|
||||||
OHIF.log.info('ProtocolEngine::findMatchByStudy');
|
OHIF.log.info('ProtocolEngine::findMatchByStudy');
|
||||||
|
|
||||||
const matched = [];
|
const matched = [];
|
||||||
const studyInstance = study.getFirstInstance();
|
const studyInstance = study.getFirstInstance();
|
||||||
|
|
||||||
|
// Set custom attribute for study metadata
|
||||||
|
const numberOfPriorsReferenced = this.getNumberOfAvailablePriors(study.getObjectID());
|
||||||
|
studyInstance.setCustomAttribute('numberOfPriorsReferenced', numberOfPriorsReferenced);
|
||||||
|
|
||||||
HP.ProtocolStore.getProtocol().forEach(protocol => {
|
HP.ProtocolStore.getProtocol().forEach(protocol => {
|
||||||
// Clone the protocol's protocolMatchingRules array
|
// Clone the protocol's protocolMatchingRules array
|
||||||
// We clone it so that we don't accidentally add the
|
// We clone it so that we don't accidentally add the
|
||||||
// numberOfPriorsReferenced rule to the Protocol itself.
|
// numberOfPriorsReferenced rule to the Protocol itself.
|
||||||
const rules = protocol.protocolMatchingRules.slice(0);
|
let rules = protocol.protocolMatchingRules.slice(0);
|
||||||
if (!rules) {
|
if (!rules) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set custom attribute for study metadata
|
// To make sure the protocol has updated priors information.
|
||||||
const numberOfPriorsReferenced = this.getNumberOfAvailablePriors(study.getObjectID());
|
// This is reasonable for Viewers that use different APIs to
|
||||||
studyInstance.setCustomAttribute('numberOfPriorsReferenced', numberOfPriorsReferenced);
|
// manage ProtocolStore or protocols.
|
||||||
|
if (!protocol.hasUpdatedPriorsInformation) {
|
||||||
|
|
||||||
const requiredPriorsRule = new HP.ProtocolMatchingRule('numberOfPriorsReferenced', {
|
// If protocol is not an instance of HP.Protocol
|
||||||
numericality: {
|
// make it be.
|
||||||
greaterThanOrEqualTo: protocol.numberOfPriorsReferenced
|
if (!(protocol instanceof HP.Protocol)) {
|
||||||
|
const protocolObject = new HP.Protocol();
|
||||||
|
protocolObject.fromObject(protocol);
|
||||||
|
protocol = protocolObject;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
rules.push(requiredPriorsRule);
|
protocol.updateNumberOfPriorsReferenced();
|
||||||
|
}
|
||||||
|
|
||||||
const exactPriorsDesiredRule = new HP.ProtocolMatchingRule('numberOfPriorsReferenced', {
|
// Skip protocols that require more priors than the study has
|
||||||
numericality: {
|
const protocolRequiresPriors = this.protocolRequiresPriors(protocol);
|
||||||
equalTo: protocol.numberOfPriorsReferenced
|
if (protocolRequiresPriors && numberOfPriorsReferenced < protocol.numberOfPriorsReferenced) {
|
||||||
},
|
return;
|
||||||
}, false, 1);
|
}
|
||||||
|
|
||||||
rules.push(exactPriorsDesiredRule);
|
// Get additional rules for number of priors referenced
|
||||||
|
const priorsRules = this.getNumberOfPriorsRules(protocol.numberOfPriorsReferenced);
|
||||||
|
// Concatenate rules
|
||||||
|
rules = rules.concat(priorsRules);
|
||||||
|
|
||||||
const matchedDetails = HP.match(studyInstance, rules);
|
// Run the matcher and get matching details
|
||||||
|
const matchedDetails = HPMatcher.match(studyInstance, rules);
|
||||||
|
const score = matchedDetails.score;
|
||||||
|
|
||||||
if (matchedDetails.score > 0) {
|
// The protocol matched some rule, add it to the matched list
|
||||||
|
if (score > 0) {
|
||||||
matched.push({
|
matched.push({
|
||||||
score: matchedDetails.score,
|
score,
|
||||||
protocol: protocol
|
protocol
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// If no matches were found, select the default protocol
|
||||||
if (!matched.length) {
|
if (!matched.length) {
|
||||||
const defaultProtocol = HP.ProtocolStore.getProtocol('defaultProtocol');
|
const defaultProtocol = HP.ProtocolStore.getProtocol('defaultProtocol');
|
||||||
|
|
||||||
@ -308,6 +223,7 @@ HP.ProtocolEngine = class ProtocolEngine {
|
|||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sort the matched list by score
|
||||||
sortByScore(matched);
|
sortByScore(matched);
|
||||||
|
|
||||||
OHIF.log.info('ProtocolEngine::findMatchByStudy matched', matched);
|
OHIF.log.info('ProtocolEngine::findMatchByStudy matched', matched);
|
||||||
@ -318,8 +234,8 @@ HP.ProtocolEngine = class ProtocolEngine {
|
|||||||
/**
|
/**
|
||||||
* Populates the MatchedProtocols Collection by running the matching procedure
|
* Populates the MatchedProtocols Collection by running the matching procedure
|
||||||
*/
|
*/
|
||||||
updateMatches() {
|
updateProtocolMatches() {
|
||||||
OHIF.log.info('ProtocolEngine::updateMatches');
|
OHIF.log.info('ProtocolEngine::updateProtocolMatches');
|
||||||
|
|
||||||
// Clear all data from the MatchedProtocols Collection
|
// Clear all data from the MatchedProtocols Collection
|
||||||
MatchedProtocols.remove({});
|
MatchedProtocols.remove({});
|
||||||
@ -341,6 +257,7 @@ HP.ProtocolEngine = class ProtocolEngine {
|
|||||||
|
|
||||||
// If it is not already in the MatchedProtocols Collection, insert it with its score
|
// If it is not already in the MatchedProtocols Collection, insert it with its score
|
||||||
if (!protocolInCollection) {
|
if (!protocolInCollection) {
|
||||||
|
OHIF.log.info('ProtocolEngine::updateProtocolMatches inserting protocol match', matchedDetail);
|
||||||
MatchedProtocols.insert(matchedDetail);
|
MatchedProtocols.insert(matchedDetail);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -351,9 +268,9 @@ HP.ProtocolEngine = class ProtocolEngine {
|
|||||||
* Return the best matched Protocol to the current study or set of studies
|
* Return the best matched Protocol to the current study or set of studies
|
||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
getBestMatch() {
|
getBestProtocolMatch() {
|
||||||
// Run the matching to populate the MatchedProtocols Collection
|
// Run the matching to populate the MatchedProtocols Collection
|
||||||
this.updateMatches();
|
this.updateProtocolMatches();
|
||||||
|
|
||||||
// Retrieve the highest scoring Protocol
|
// Retrieve the highest scoring Protocol
|
||||||
const sorted = MatchedProtocols.find({}, {
|
const sorted = MatchedProtocols.find({}, {
|
||||||
@ -366,7 +283,8 @@ HP.ProtocolEngine = class ProtocolEngine {
|
|||||||
// Highest scoring Protocol
|
// Highest scoring Protocol
|
||||||
const bestMatch = sorted[0].protocol;
|
const bestMatch = sorted[0].protocol;
|
||||||
|
|
||||||
OHIF.log.info('ProtocolEngine::getBestMatch bestMatch', bestMatch);
|
OHIF.log.info('ProtocolEngine::getBestProtocolMatch bestMatch', bestMatch);
|
||||||
|
|
||||||
return bestMatch;
|
return bestMatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -378,6 +296,7 @@ HP.ProtocolEngine = class ProtocolEngine {
|
|||||||
*/
|
*/
|
||||||
getNumberOfAvailablePriors(studyObjectID) {
|
getNumberOfAvailablePriors(studyObjectID) {
|
||||||
const priors = this.getAvailableStudyPriors(studyObjectID);
|
const priors = this.getAvailableStudyPriors(studyObjectID);
|
||||||
|
|
||||||
return priors.length;
|
return priors.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -389,6 +308,7 @@ HP.ProtocolEngine = class ProtocolEngine {
|
|||||||
*/
|
*/
|
||||||
getAvailableStudyPriors(studyObjectID) {
|
getAvailableStudyPriors(studyObjectID) {
|
||||||
const priors = this.priorStudies.get(studyObjectID);
|
const priors = this.priorStudies.get(studyObjectID);
|
||||||
|
|
||||||
return priors instanceof Array ? priors : [];
|
return priors instanceof Array ? priors : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -407,7 +327,7 @@ HP.ProtocolEngine = class ProtocolEngine {
|
|||||||
if (prior instanceof StudyMetadata) {
|
if (prior instanceof StudyMetadata) {
|
||||||
prior = prior.getFirstInstance();
|
prior = prior.getFirstInstance();
|
||||||
}
|
}
|
||||||
const matchDetails = HP.match(prior, protocolMatchingRules);
|
const matchDetails = HPMatcher.match(prior, protocolMatchingRules);
|
||||||
return matchDetails.score > 0;
|
return matchDetails.score > 0;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -416,7 +336,7 @@ HP.ProtocolEngine = class ProtocolEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Match images given a list of Studies and a Viewport's image matching reqs
|
// Match images given a list of Studies and a Viewport's image matching reqs
|
||||||
matchImages(viewport) {
|
matchImages(viewport, viewportIndex) {
|
||||||
OHIF.log.info('ProtocolEngine::matchImages');
|
OHIF.log.info('ProtocolEngine::matchImages');
|
||||||
|
|
||||||
const { studyMatchingRules, seriesMatchingRules, imageMatchingRules: instanceMatchingRules } = viewport;
|
const { studyMatchingRules, seriesMatchingRules, imageMatchingRules: instanceMatchingRules } = viewport;
|
||||||
@ -431,13 +351,13 @@ HP.ProtocolEngine = class ProtocolEngine {
|
|||||||
let bestMatch;
|
let bestMatch;
|
||||||
|
|
||||||
// Set custom attribute for study metadata and it's first instance
|
// Set custom attribute for study metadata and it's first instance
|
||||||
currentStudy.setCustomAttribute('abstractPriorValue', 0);
|
currentStudy.setCustomAttribute(ABSTRACT_PRIOR_VALUE, 0);
|
||||||
if (firstInstance instanceof InstanceMetadata) {
|
if (firstInstance instanceof InstanceMetadata) {
|
||||||
firstInstance.setCustomAttribute('abstractPriorValue', 0);
|
firstInstance.setCustomAttribute(ABSTRACT_PRIOR_VALUE, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
studyMatchingRules.forEach(rule => {
|
studyMatchingRules.forEach(rule => {
|
||||||
if (rule.attribute === 'abstractPriorValue') {
|
if (rule.attribute === ABSTRACT_PRIOR_VALUE) {
|
||||||
const validatorType = Object.keys(rule.constraint)[0];
|
const validatorType = Object.keys(rule.constraint)[0];
|
||||||
const validator = Object.keys(rule.constraint[validatorType])[0];
|
const validator = Object.keys(rule.constraint[validatorType])[0];
|
||||||
|
|
||||||
@ -473,19 +393,19 @@ HP.ProtocolEngine = class ProtocolEngine {
|
|||||||
// Get study metadata if necessary and load study in the viewer (each viewer should provide it's own load study method)
|
// Get study metadata if necessary and load study in the viewer (each viewer should provide it's own load study method)
|
||||||
this.studyMetadataSource.loadStudy(priorStudy).then(studyMetadata => {
|
this.studyMetadataSource.loadStudy(priorStudy).then(studyMetadata => {
|
||||||
// Set the custom attribute abstractPriorValue for the study metadata
|
// Set the custom attribute abstractPriorValue for the study metadata
|
||||||
studyMetadata.setCustomAttribute('abstractPriorValue', abstractPriorValue);
|
studyMetadata.setCustomAttribute(ABSTRACT_PRIOR_VALUE, abstractPriorValue);
|
||||||
|
|
||||||
// Also add custom attribute
|
// Also add custom attribute
|
||||||
const firstInstance = studyMetadata.getFirstInstance();
|
const firstInstance = studyMetadata.getFirstInstance();
|
||||||
if (firstInstance instanceof InstanceMetadata) {
|
if (firstInstance instanceof InstanceMetadata) {
|
||||||
firstInstance.setCustomAttribute('abstractPriorValue', abstractPriorValue);
|
firstInstance.setCustomAttribute(ABSTRACT_PRIOR_VALUE, abstractPriorValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert the new study metadata
|
// Insert the new study metadata
|
||||||
this.studies.push(studyMetadata);
|
this.studies.push(studyMetadata);
|
||||||
|
|
||||||
// Update the viewport to refresh layout manager with new study
|
// Update the viewport to refresh layout manager with new study
|
||||||
this.updateViewports();
|
this.updateViewports(viewportIndex);
|
||||||
}, error => {
|
}, error => {
|
||||||
OHIF.log.warn(error);
|
OHIF.log.warn(error);
|
||||||
throw new OHIFError(`ProtocolEngine::matchImages could not get study metadata for the Study with the following ObjectID: ${priorStudyObjectID}`);
|
throw new OHIFError(`ProtocolEngine::matchImages could not get study metadata for the Study with the following ObjectID: ${priorStudyObjectID}`);
|
||||||
@ -495,7 +415,7 @@ HP.ProtocolEngine = class ProtocolEngine {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.studies.forEach(study => {
|
this.studies.forEach(study => {
|
||||||
const studyMatchDetails = HP.match(study.getFirstInstance(), studyMatchingRules);
|
const studyMatchDetails = HPMatcher.match(study.getFirstInstance(), studyMatchingRules);
|
||||||
if ((studyMatchingRules.length && !studyMatchDetails.score) ||
|
if ((studyMatchingRules.length && !studyMatchDetails.score) ||
|
||||||
studyMatchDetails.score < highestStudyMatchingScore) {
|
studyMatchDetails.score < highestStudyMatchingScore) {
|
||||||
return;
|
return;
|
||||||
@ -504,7 +424,7 @@ HP.ProtocolEngine = class ProtocolEngine {
|
|||||||
highestStudyMatchingScore = studyMatchDetails.score;
|
highestStudyMatchingScore = studyMatchDetails.score;
|
||||||
|
|
||||||
study.forEachSeries(series => {
|
study.forEachSeries(series => {
|
||||||
const seriesMatchDetails = HP.match(series.getFirstInstance(), seriesMatchingRules);
|
const seriesMatchDetails = HPMatcher.match(series.getFirstInstance(), seriesMatchingRules);
|
||||||
if ((seriesMatchingRules.length && !seriesMatchDetails.score) ||
|
if ((seriesMatchingRules.length && !seriesMatchDetails.score) ||
|
||||||
seriesMatchDetails.score < highestSeriesMatchingScore) {
|
seriesMatchDetails.score < highestSeriesMatchingScore) {
|
||||||
return;
|
return;
|
||||||
@ -522,7 +442,7 @@ HP.ProtocolEngine = class ProtocolEngine {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const instanceMatchDetails = HP.match(instance, instanceMatchingRules);
|
const instanceMatchDetails = HPMatcher.match(instance, instanceMatchingRules);
|
||||||
|
|
||||||
const matchDetails = {
|
const matchDetails = {
|
||||||
passed: [],
|
passed: [],
|
||||||
@ -609,7 +529,7 @@ HP.ProtocolEngine = class ProtocolEngine {
|
|||||||
OHIF.log.info(`ProtocolEngine::updateViewports viewportIndex: ${viewportIndex}`);
|
OHIF.log.info(`ProtocolEngine::updateViewports viewportIndex: ${viewportIndex}`);
|
||||||
|
|
||||||
// Make sure we have an active protocol with a non-empty array of display sets
|
// Make sure we have an active protocol with a non-empty array of display sets
|
||||||
if (!this.protocol || !this.protocol.stages || !this.protocol.stages.length) {
|
if (!this.getNumProtocolStages()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -649,7 +569,7 @@ HP.ProtocolEngine = class ProtocolEngine {
|
|||||||
|
|
||||||
// Loop through each viewport
|
// Loop through each viewport
|
||||||
stageModel.viewports.forEach((viewport, viewportIndex) => {
|
stageModel.viewports.forEach((viewport, viewportIndex) => {
|
||||||
const details = this.matchImages(viewport);
|
const details = this.matchImages(viewport, viewportIndex);
|
||||||
|
|
||||||
this.matchDetails[viewportIndex] = details;
|
this.matchDetails[viewportIndex] = details;
|
||||||
|
|
||||||
@ -747,7 +667,7 @@ HP.ProtocolEngine = class ProtocolEngine {
|
|||||||
* @param newProtocol
|
* @param newProtocol
|
||||||
* @param updateViewports
|
* @param updateViewports
|
||||||
*/
|
*/
|
||||||
setHangingProtocol(newProtocol, updateViewports=true) {
|
setHangingProtocol(newProtocol, updateViewports = true) {
|
||||||
OHIF.log.info('ProtocolEngine::setHangingProtocol newProtocol', newProtocol);
|
OHIF.log.info('ProtocolEngine::setHangingProtocol newProtocol', newProtocol);
|
||||||
OHIF.log.info(`ProtocolEngine::setHangingProtocol updateViewports = ${updateViewports}`);
|
OHIF.log.info(`ProtocolEngine::setHangingProtocol updateViewports = ${updateViewports}`);
|
||||||
|
|
||||||
@ -783,30 +703,64 @@ HP.ProtocolEngine = class ProtocolEngine {
|
|||||||
selected: true
|
selected: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Session.set('HangingProtocolName', this.protocol.name);
|
||||||
|
Session.set('HangingProtocolStage', this.stage);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Changes the current stage to a new stage index in the display set sequence
|
* Check if the next stage is available
|
||||||
*
|
* @return {Boolean} True if next stage is available or false otherwise
|
||||||
* @param stage An integer value specifying the index of the desired Stage
|
|
||||||
*/
|
*/
|
||||||
setCurrentProtocolStage(stage) {
|
isNextStageAvailable() {
|
||||||
OHIF.log.info(`ProtocolEngine::setCurrentProtocolStage stage = ${stage}`);
|
const numberOfStages = this.getNumProtocolStages();
|
||||||
|
|
||||||
if (!this.protocol || !this.protocol.stages || !this.protocol.stages.length) {
|
return this.stage + 1 < numberOfStages;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (stage >= this.protocol.stages.length) {
|
/**
|
||||||
return;
|
* Check if the previous stage is available
|
||||||
}
|
* @return {Boolean} True if previous stage is available or false otherwise
|
||||||
|
*/
|
||||||
this.stage = stage;
|
isPreviousStageAvailable() {
|
||||||
this.updateViewports();
|
return this.stage - 1 >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the number of Stages in the current Protocol
|
* Changes the current stage to a new stage index in the display set sequence.
|
||||||
|
* It checks if the next stage exists.
|
||||||
|
*
|
||||||
|
* @param {Integer} stageAction An integer value specifying wheater next (1) or previous (-1) stage
|
||||||
|
* @return {Boolean} True if new stage has set or false, otherwise
|
||||||
|
*/
|
||||||
|
setCurrentProtocolStage(stageAction) {
|
||||||
|
// Check if previous or next stage is available
|
||||||
|
if (stageAction === -1 && !this.isPreviousStageAvailable()) {
|
||||||
|
return false;
|
||||||
|
} else if (stageAction === 1 && !this.isNextStageAvailable()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets the new stage
|
||||||
|
this.stage += stageAction;
|
||||||
|
|
||||||
|
// Log the new stage
|
||||||
|
OHIF.log.info(`ProtocolEngine::setCurrentProtocolStage stage = ${this.stage}`);
|
||||||
|
|
||||||
|
// Set stage Session variable for reactivity
|
||||||
|
Session.set('HangingProtocolStage', this.stage);
|
||||||
|
|
||||||
|
// Since stage has changed, we need to update the viewports
|
||||||
|
// and redo matchings
|
||||||
|
this.updateViewports();
|
||||||
|
|
||||||
|
// Everything went well
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the number of Stages in the current Protocol or
|
||||||
|
* undefined if no protocol or stages are set
|
||||||
*/
|
*/
|
||||||
getNumProtocolStages() {
|
getNumProtocolStages() {
|
||||||
if (!this.protocol || !this.protocol.stages || !this.protocol.stages.length) {
|
if (!this.protocol || !this.protocol.stages || !this.protocol.stages.length) {
|
||||||
@ -820,13 +774,23 @@ HP.ProtocolEngine = class ProtocolEngine {
|
|||||||
* Switches to the next protocol stage in the display set sequence
|
* Switches to the next protocol stage in the display set sequence
|
||||||
*/
|
*/
|
||||||
nextProtocolStage() {
|
nextProtocolStage() {
|
||||||
this.setCurrentProtocolStage(++this.stage);
|
OHIF.log.info('ProtocolEngine::nextProtocolStage');
|
||||||
|
|
||||||
|
if (!this.setCurrentProtocolStage(1)) {
|
||||||
|
// Just for logging purpose
|
||||||
|
OHIF.log.info('ProtocolEngine::nextProtocolStage failed');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Switches to the previous protocol stage in the display set sequence
|
* Switches to the previous protocol stage in the display set sequence
|
||||||
*/
|
*/
|
||||||
previousProtocolStage() {
|
previousProtocolStage() {
|
||||||
this.setCurrentProtocolStage(--this.stage);
|
OHIF.log.info('ProtocolEngine::previousProtocolStage');
|
||||||
|
|
||||||
|
if (!this.setCurrentProtocolStage(-1)) {
|
||||||
|
// Just for logging purpose
|
||||||
|
OHIF.log.info('ProtocolEngine::previousProtocolStage failed');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -288,14 +288,19 @@ const stopAllClips = () => {
|
|||||||
|
|
||||||
|
|
||||||
const isStackScrollLinkingDisabled = () => {
|
const isStackScrollLinkingDisabled = () => {
|
||||||
|
let linkableViewportsCount = 0;
|
||||||
|
|
||||||
// Its called everytime active viewport and/or layout change
|
// Its called everytime active viewport and/or layout change
|
||||||
Session.get('viewportActivated');
|
Session.get('viewportActivated');
|
||||||
Session.get('LayoutManagerUpdated');
|
Session.get('LayoutManagerUpdated');
|
||||||
|
|
||||||
const synchronizer = OHIF.viewer.stackImagePositionOffsetSynchronizer;
|
const synchronizer = OHIF.viewer.stackImagePositionOffsetSynchronizer;
|
||||||
const linkableViewports = synchronizer.getLinkableViewports();
|
if (synchronizer) {
|
||||||
|
const linkableViewports = synchronizer.getLinkableViewports();
|
||||||
|
linkableViewportsCount = linkableViewports.length;
|
||||||
|
}
|
||||||
|
|
||||||
return linkableViewports.length <= 1;
|
return linkableViewportsCount <= 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create an event listener to update playing state when a clip stops playing
|
// Create an event listener to update playing state when a clip stops playing
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user