Hanging protocol improvements on matching process and other improvements
This commit is contained in:
parent
744a0bd76e
commit
35b855de56
243
Packages/ohif-hanging-protocols/both/classes/Protocol.js
Normal file
243
Packages/ohif-hanging-protocols/both/classes/Protocol.js
Normal file
@ -0,0 +1,243 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Random } from 'meteor/random';
|
||||
|
||||
// Local imports
|
||||
import { removeFromArray } from '../lib/removeFromArray';
|
||||
|
||||
/**
|
||||
* This class represents a Hanging Protocol at the highest level
|
||||
*
|
||||
* @type {Protocol}
|
||||
*/
|
||||
HP.Protocol = class Protocol {
|
||||
/**
|
||||
* The Constructor for the Class to create a Protocol with the bare
|
||||
* minimum information
|
||||
*
|
||||
* @param name The desired name for the Protocol
|
||||
*/
|
||||
constructor(name) {
|
||||
// Create a new UUID for this Protocol
|
||||
this.id = Random.id();
|
||||
|
||||
// Store a value which determines whether or not a Protocol is locked
|
||||
// This is probably temporary, since we will eventually have role / user
|
||||
// checks for editing. For now we just need it to prevent changes to the
|
||||
// default protocols.
|
||||
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
|
||||
this.name = name;
|
||||
|
||||
// Set the created and modified dates to Now
|
||||
this.createdDate = new Date();
|
||||
this.modifiedDate = new Date();
|
||||
|
||||
// If we are logged in while creating this Protocol,
|
||||
// store this information as well
|
||||
if (Meteor.users && Meteor.userId) {
|
||||
this.createdBy = Meteor.userId;
|
||||
this.modifiedBy = Meteor.userId;
|
||||
}
|
||||
|
||||
// Create two empty Sets specifying which roles
|
||||
// have read and write access to this Protocol
|
||||
this.availableTo = new Set();
|
||||
this.editableBy = new Set();
|
||||
|
||||
// Define empty arrays for the Protocol matching rules
|
||||
// and Stages
|
||||
this.protocolMatchingRules = [];
|
||||
this.stages = [];
|
||||
|
||||
// Define auxiliary values for priors
|
||||
this.numberOfPriorsReferenced = -1;
|
||||
}
|
||||
|
||||
getNumberOfPriorsReferenced(skipCache = false) {
|
||||
let numberOfPriorsReferenced = skipCache !== true ? this.numberOfPriorsReferenced : -1;
|
||||
|
||||
// Check if information is cached already
|
||||
if (numberOfPriorsReferenced > -1) {
|
||||
return numberOfPriorsReferenced;
|
||||
}
|
||||
|
||||
numberOfPriorsReferenced = 0;
|
||||
|
||||
// Search each study matching rule for prior rules
|
||||
// Each stage can have many viewports that can have
|
||||
// multiple study matching rules.
|
||||
this.stages.forEach(stage => {
|
||||
if (!stage.viewports) {
|
||||
return;
|
||||
}
|
||||
|
||||
stage.viewports.forEach(viewport => {
|
||||
if (!viewport.studyMatchingRules) {
|
||||
return;
|
||||
}
|
||||
|
||||
viewport.studyMatchingRules.forEach(rule => {
|
||||
// If the current rule is not a priors rule, it will return -1 then numberOfPriorsReferenced will continue to be 0
|
||||
const priorsReferenced = rule.getNumberOfPriorsReferenced();
|
||||
if (priorsReferenced > numberOfPriorsReferenced) {
|
||||
numberOfPriorsReferenced = priorsReferenced;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
this.numberOfPriorsReferenced = numberOfPriorsReferenced;
|
||||
|
||||
return numberOfPriorsReferenced
|
||||
}
|
||||
|
||||
updateNumberOfPriorsReferenced() {
|
||||
this.getNumberOfPriorsReferenced(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to update the modifiedDate when the Protocol
|
||||
* has been changed
|
||||
*/
|
||||
protocolWasModified() {
|
||||
// If we are logged in while modifying this Protocol,
|
||||
// store this information as well
|
||||
if (Meteor.users && 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();
|
||||
|
||||
// Update the modifiedDate with the current Date/Time
|
||||
this.modifiedDate = new Date();
|
||||
}
|
||||
|
||||
/**
|
||||
* Occasionally the Protocol class needs to be instantiated from a JavaScript Object
|
||||
* containing the Protocol data. This function fills in a Protocol with the Object
|
||||
* data.
|
||||
*
|
||||
* @param input A Protocol as a JavaScript Object, e.g. retrieved from MongoDB or JSON
|
||||
*/
|
||||
fromObject(input) {
|
||||
// Check if the input already has an ID
|
||||
// If so, keep it. It not, create a new UUID
|
||||
this.id = input.id || Random.id();
|
||||
|
||||
// Assign the input name to the Protocol
|
||||
this.name = input.name;
|
||||
|
||||
// Retrieve locked status, use !! to make it truthy
|
||||
// so that undefined values will be set to false
|
||||
this.locked = !!input.locked;
|
||||
|
||||
// TODO: Check how to regenerate Set from Object
|
||||
//this.availableTo = new Set(input.availableTo);
|
||||
//this.editableBy = new Set(input.editableBy);
|
||||
|
||||
// If the input contains Protocol matching rules
|
||||
if (input.protocolMatchingRules) {
|
||||
input.protocolMatchingRules.forEach(ruleObject => {
|
||||
// Create new Rules from the stored data
|
||||
var rule = new HP.ProtocolMatchingRule();
|
||||
rule.fromObject(ruleObject);
|
||||
|
||||
// Add them to the Protocol
|
||||
this.protocolMatchingRules.push(rule);
|
||||
});
|
||||
}
|
||||
|
||||
// If the input contains data for various Stages in the
|
||||
// display set sequence
|
||||
if (input.stages) {
|
||||
input.stages.forEach(stageObject => {
|
||||
// Create Stages from the stored data
|
||||
var stage = new HP.Stage();
|
||||
stage.fromObject(stageObject);
|
||||
|
||||
// Add them to the Protocol
|
||||
this.stages.push(stage);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a clone of the current Protocol with a new name
|
||||
*
|
||||
* @param name
|
||||
* @returns {Protocol|*}
|
||||
*/
|
||||
createClone(name) {
|
||||
// Create a new JavaScript independent of the current Protocol
|
||||
var currentProtocol = Object.assign({}, this);
|
||||
|
||||
// Create a new Protocol to return
|
||||
var clonedProtocol = new HP.Protocol();
|
||||
|
||||
// Apply the desired properties
|
||||
currentProtocol.id = clonedProtocol.id;
|
||||
clonedProtocol.fromObject(currentProtocol);
|
||||
|
||||
// If we have specified a name, assign it
|
||||
if (name) {
|
||||
clonedProtocol.name = name;
|
||||
}
|
||||
|
||||
// Unlock the clone
|
||||
clonedProtocol.locked = false;
|
||||
|
||||
// Return the cloned Protocol
|
||||
return clonedProtocol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a Stage to this Protocol's display set sequence
|
||||
*
|
||||
* @param stage
|
||||
*/
|
||||
addStage(stage) {
|
||||
this.stages.push(stage);
|
||||
|
||||
// Update the modifiedDate and User that last
|
||||
// modified this Protocol
|
||||
this.protocolWasModified();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a Rule to this Protocol's array of matching rules
|
||||
*
|
||||
* @param rule
|
||||
*/
|
||||
addProtocolMatchingRule(rule) {
|
||||
this.protocolMatchingRules.push(rule);
|
||||
|
||||
// Update the modifiedDate and User that last
|
||||
// modified this Protocol
|
||||
this.protocolWasModified();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a Rule from this Protocol's array of matching rules
|
||||
*
|
||||
* @param rule
|
||||
*/
|
||||
removeProtocolMatchingRule(rule) {
|
||||
var wasRemoved = removeFromArray(this.protocolMatchingRules, rule);
|
||||
|
||||
// Update the modifiedDate and User that last
|
||||
// modified this Protocol
|
||||
if (wasRemoved) {
|
||||
this.protocolWasModified();
|
||||
}
|
||||
}
|
||||
};
|
||||
173
Packages/ohif-hanging-protocols/both/classes/Rule.js
Normal file
173
Packages/ohif-hanging-protocols/both/classes/Rule.js
Normal file
@ -0,0 +1,173 @@
|
||||
import { Random } from 'meteor/random';
|
||||
|
||||
import { comparators } from '../lib/comparators';
|
||||
|
||||
const EQUALS_REGEXP = /^equals$/;
|
||||
|
||||
/**
|
||||
* This Class represents a Rule to be evaluated given a set of attributes
|
||||
* Rules have:
|
||||
* - An attribute (e.g. 'seriesDescription')
|
||||
* - A constraint Object, in the form required by Validate.js:
|
||||
*
|
||||
* rule.constraint = {
|
||||
* contains: {
|
||||
* value: 'T-1'
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* Note: In this example we use the 'contains' Validator, which is a custom Validator defined in Viewerbase
|
||||
*
|
||||
* - A value for whether or not they are Required to be matched (default: False)
|
||||
* - A value for their relative weighting during Protocol or Image matching (default: 1)
|
||||
*/
|
||||
export class Rule {
|
||||
/**
|
||||
* The Constructor for the Class to create a Rule with the bare
|
||||
* minimum information
|
||||
*
|
||||
* @param name The desired name for the Rule
|
||||
*/
|
||||
constructor(attribute, constraint, required, weight) {
|
||||
// Create a new UUID for this Rule
|
||||
this.id = Random.id();
|
||||
|
||||
// Set the Rule's weight (defaults to 1)
|
||||
this.weight = weight || 1;
|
||||
|
||||
// If an attribute is specified, assign it
|
||||
if (attribute) {
|
||||
this.attribute = attribute;
|
||||
}
|
||||
|
||||
// If a constraint is specified, assign it
|
||||
if (constraint) {
|
||||
this.constraint = constraint;
|
||||
}
|
||||
|
||||
// If a value for 'required' is specified, assign it
|
||||
if (required === undefined) {
|
||||
// If no value was specified, default to False
|
||||
this.required = false;
|
||||
} else {
|
||||
this.required = required;
|
||||
}
|
||||
|
||||
// Cache for constraint info object
|
||||
this._constraintInfo = void 0;
|
||||
|
||||
// Cache for validator and value object
|
||||
this._validatorAndValue = void 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Occasionally the Rule class needs to be instantiated from a JavaScript Object.
|
||||
* This function fills in a Protocol with the Object data.
|
||||
*
|
||||
* @param input A Rule as a JavaScript Object, e.g. retrieved from MongoDB or JSON
|
||||
*/
|
||||
fromObject(input) {
|
||||
// Check if the input already has an ID
|
||||
// If so, keep it. It not, create a new UUID
|
||||
this.id = input.id || Random.id();
|
||||
|
||||
// Assign the specified input data to the Rule
|
||||
this.required = input.required;
|
||||
this.weight = input.weight;
|
||||
this.attribute = input.attribute;
|
||||
this.constraint = input.constraint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the constraint info object for the current constraint
|
||||
* @return {Object\undefined} Constraint object or undefined if current constraint
|
||||
* is not valid or not found in comparators list
|
||||
*/
|
||||
getConstraintInfo() {
|
||||
let constraintInfo = this._constraintInfo;
|
||||
// Check if info is cached already
|
||||
if (constraintInfo !== void 0) {
|
||||
return constraintInfo;
|
||||
}
|
||||
|
||||
const ruleConstraint = Object.keys(this.constraint)[0];
|
||||
|
||||
if (ruleConstraint !== void 0) {
|
||||
constraintInfo = comparators.find(comparator => ruleConstraint === comparator.id)
|
||||
}
|
||||
|
||||
// Cache this information for later use
|
||||
this._constraintInfo = constraintInfo;
|
||||
|
||||
return constraintInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if current rule is related to priors
|
||||
* @return {Boolean} True if a rule is related to priors or false otherwise
|
||||
*/
|
||||
isRuleForPrior() {
|
||||
// @TODO: Should we check this too? this.attribute === 'relativeTime'
|
||||
return this.attribute === 'abstractPriorValue';
|
||||
}
|
||||
|
||||
/**
|
||||
* If the current rule is a rule for priors, returns the number of referenced priors. Otherwise, returns -1.
|
||||
* @return {Number} The number of referenced priors or -1 if not applicable. Returns zero if the actual value could not be determined.
|
||||
*/
|
||||
getNumberOfPriorsReferenced() {
|
||||
if (!this.isRuleForPrior()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Get rule's validator and value
|
||||
const ruleValidatorAndValue = this.getConstraintValidatorAndValue();
|
||||
const { value, validator } = ruleValidatorAndValue;
|
||||
const intValue = parseInt(value, 10) || 0; // avoid possible NaN
|
||||
|
||||
// "Equal to" validators
|
||||
if (EQUALS_REGEXP.test(validator)) {
|
||||
// In this case, -1 (the oldest prior) indicates that at least one study is used
|
||||
return intValue < 0 ? 1 : intValue;
|
||||
}
|
||||
|
||||
// Default cases return value
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the constraint validator and value
|
||||
* @return {Object|undefined} Returns an object containing the validator and it's value or undefined
|
||||
*/
|
||||
getConstraintValidatorAndValue() {
|
||||
let validatorAndValue = this._validatorAndValue;
|
||||
|
||||
// Check if validator and value are cached already
|
||||
if (validatorAndValue !== void 0) {
|
||||
return validatorAndValue;
|
||||
}
|
||||
|
||||
// Get the constraint info object
|
||||
const constraintInfo = this.getConstraintInfo();
|
||||
|
||||
// Constraint info object exists and is valid
|
||||
if (constraintInfo !== void 0) {
|
||||
const validator = constraintInfo.validator;
|
||||
const currentValidator = this.constraint[validator];
|
||||
|
||||
if (currentValidator) {
|
||||
const constraintValidator = constraintInfo.validatorOption;
|
||||
const constraintValue = currentValidator[constraintValidator];
|
||||
|
||||
validatorAndValue = {
|
||||
value: constraintValue,
|
||||
validator: constraintInfo.id
|
||||
};
|
||||
|
||||
this._validatorAndValue = validatorAndValue;
|
||||
}
|
||||
}
|
||||
|
||||
return validatorAndValue;
|
||||
}
|
||||
}
|
||||
87
Packages/ohif-hanging-protocols/both/classes/Stage.js
Normal file
87
Packages/ohif-hanging-protocols/both/classes/Stage.js
Normal file
@ -0,0 +1,87 @@
|
||||
import { Random } from 'meteor/random';
|
||||
|
||||
/**
|
||||
* A Stage is one step in the Display Set Sequence for a Hanging Protocol
|
||||
*
|
||||
* Stages are defined as a ViewportStructure and an array of Viewports
|
||||
*
|
||||
* @type {Stage}
|
||||
*/
|
||||
HP.Stage = class Stage {
|
||||
constructor(ViewportStructure, name) {
|
||||
// Create a new UUID for this Stage
|
||||
this.id = Random.id();
|
||||
|
||||
// Assign the name and ViewportStructure provided
|
||||
this.name = name;
|
||||
this.viewportStructure = ViewportStructure;
|
||||
|
||||
// Create an empty array for the Viewports
|
||||
this.viewports = [];
|
||||
|
||||
// Set the created date to Now
|
||||
this.createdDate = new Date();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a clone of the current Stage with a new name
|
||||
*
|
||||
* Note! This method absolutely cannot be renamed 'clone', because
|
||||
* Minimongo's insert method uses 'clone' internally and this
|
||||
* somehow causes very bizarre behaviour
|
||||
*
|
||||
* @param name
|
||||
* @returns {Stage|*}
|
||||
*/
|
||||
createClone(name) {
|
||||
// Create a new JavaScript independent of the current Protocol
|
||||
var currentStage = Object.assign({}, this);
|
||||
|
||||
// Create a new Stage to return
|
||||
var clonedStage = new HP.Stage();
|
||||
|
||||
// Assign the desired properties
|
||||
currentStage.id = clonedStage.id;
|
||||
clonedStage.fromObject(currentStage);
|
||||
|
||||
// If we have specified a name, assign it
|
||||
if (name) {
|
||||
clonedStage.name = name;
|
||||
}
|
||||
|
||||
// Return the cloned Stage
|
||||
return clonedStage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Occasionally the Stage class needs to be instantiated from a JavaScript Object.
|
||||
* This function fills in a Protocol with the Object data.
|
||||
*
|
||||
* @param input A Stage as a JavaScript Object, e.g. retrieved from MongoDB or JSON
|
||||
*/
|
||||
fromObject(input) {
|
||||
// Check if the input already has an ID
|
||||
// If so, keep it. It not, create a new UUID
|
||||
this.id = input.id || Random.id();
|
||||
|
||||
// Assign the input name to the Stage
|
||||
this.name = input.name;
|
||||
|
||||
// If a ViewportStructure is present in the input, add it from the
|
||||
// input data
|
||||
this.viewportStructure = new HP.ViewportStructure();
|
||||
this.viewportStructure.fromObject(input.viewportStructure);
|
||||
|
||||
// If any viewports are present in the input object
|
||||
if (input.viewports) {
|
||||
input.viewports.forEach(viewportObject => {
|
||||
// Create a new Viewport with their data
|
||||
var viewport = new HP.Viewport();
|
||||
viewport.fromObject(viewportObject);
|
||||
|
||||
// Add it to the viewports array
|
||||
this.viewports.push(viewport);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
81
Packages/ohif-hanging-protocols/both/classes/Viewport.js
Normal file
81
Packages/ohif-hanging-protocols/both/classes/Viewport.js
Normal file
@ -0,0 +1,81 @@
|
||||
// Local imports
|
||||
import { removeFromArray } from '../lib/removeFromArray';
|
||||
|
||||
/**
|
||||
* This Class defines a Viewport in the Hanging Protocol Stage. A Viewport contains
|
||||
* arrays of Rules that are matched in the ProtocolEngine in order to determine which
|
||||
* images should be hung.
|
||||
*
|
||||
* @type {Viewport}
|
||||
*/
|
||||
HP.Viewport = class Viewport {
|
||||
constructor() {
|
||||
this.viewportSettings = {};
|
||||
this.imageMatchingRules = [];
|
||||
this.seriesMatchingRules = [];
|
||||
this.studyMatchingRules = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Occasionally the Viewport class needs to be instantiated from a JavaScript Object.
|
||||
* This function fills in a Viewport with the Object data.
|
||||
*
|
||||
* @param input The Viewport as a JavaScript Object, e.g. retrieved from MongoDB or JSON
|
||||
*/
|
||||
fromObject(input) {
|
||||
// If ImageMatchingRules exist, create them from the Object data
|
||||
// and add them to the Viewport's imageMatchingRules array
|
||||
if (input.imageMatchingRules) {
|
||||
input.imageMatchingRules.forEach(ruleObject => {
|
||||
var rule = new HP.ImageMatchingRule();
|
||||
rule.fromObject(ruleObject);
|
||||
this.imageMatchingRules.push(rule);
|
||||
});
|
||||
}
|
||||
|
||||
// If SeriesMatchingRules exist, create them from the Object data
|
||||
// and add them to the Viewport's seriesMatchingRules array
|
||||
if (input.seriesMatchingRules) {
|
||||
input.seriesMatchingRules.forEach(ruleObject => {
|
||||
var rule = new HP.SeriesMatchingRule();
|
||||
rule.fromObject(ruleObject);
|
||||
this.seriesMatchingRules.push(rule);
|
||||
});
|
||||
}
|
||||
|
||||
// If StudyMatchingRules exist, create them from the Object data
|
||||
// and add them to the Viewport's studyMatchingRules array
|
||||
if (input.studyMatchingRules) {
|
||||
input.studyMatchingRules.forEach(ruleObject => {
|
||||
var rule = new HP.StudyMatchingRule();
|
||||
rule.fromObject(ruleObject);
|
||||
this.studyMatchingRules.push(rule);
|
||||
});
|
||||
}
|
||||
|
||||
// If ViewportSettings exist, add them to the current protocol
|
||||
if (input.viewportSettings) {
|
||||
this.viewportSettings = input.viewportSettings;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and removes a rule from whichever array it exists in.
|
||||
* It is not required to specify if it exists in studyMatchingRules,
|
||||
* seriesMatchingRules, or imageMatchingRules
|
||||
*
|
||||
* @param rule
|
||||
*/
|
||||
removeRule(rule) {
|
||||
var array;
|
||||
if (rule instanceof HP.StudyMatchingRule) {
|
||||
array = this.studyMatchingRules;
|
||||
} else if (rule instanceof HP.SeriesMatchingRule) {
|
||||
array = this.seriesMatchingRules;
|
||||
} else if (rule instanceof HP.ImageMatchingRule) {
|
||||
array = this.imageMatchingRules;
|
||||
}
|
||||
|
||||
removeFromArray(array, rule);
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,53 @@
|
||||
/**
|
||||
* The ViewportStructure class represents the layout and layout properties that
|
||||
* Viewports are displayed in. ViewportStructure has a type, which corresponds to
|
||||
* a layout template, and a set of properties, which depend on the type.
|
||||
*
|
||||
* @type {ViewportStructure}
|
||||
*/
|
||||
HP.ViewportStructure = class ViewportStructure {
|
||||
constructor(type, properties) {
|
||||
this.type = type;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Occasionally the ViewportStructure class needs to be instantiated from a JavaScript Object.
|
||||
* This function fills in a ViewportStructure with the Object data.
|
||||
*
|
||||
* @param input The ViewportStructure as a JavaScript Object, e.g. retrieved from MongoDB or JSON
|
||||
*/
|
||||
fromObject(input) {
|
||||
this.type = input.type;
|
||||
this.properties = input.properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the layout template name based on the layout type
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
getLayoutTemplateName() {
|
||||
// Viewport structure can be updated later when we build more complex display layouts
|
||||
switch (this.type) {
|
||||
case 'grid':
|
||||
return 'gridLayout';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the number of Viewports required for this layout
|
||||
* given the layout type and properties
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
getNumViewports() {
|
||||
// Viewport structure can be updated later when we build more complex display layouts
|
||||
switch (this.type) {
|
||||
case 'grid':
|
||||
// For the typical grid layout, we only need to multiply rows by columns to
|
||||
// obtain the number of viewports
|
||||
return this.properties.rows * this.properties.columns;
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,9 @@
|
||||
import { Rule } from '../Rule';
|
||||
|
||||
/**
|
||||
* The ImageMatchingRule class extends the Rule Class.
|
||||
*
|
||||
* At present it does not add any new methods or attributes
|
||||
* @type {ImageMatchingRule}
|
||||
*/
|
||||
HP.ImageMatchingRule = class ImageMatchingRule extends Rule {};
|
||||
@ -0,0 +1,9 @@
|
||||
import { Rule } from '../Rule';
|
||||
|
||||
/**
|
||||
* The ProtocolMatchingRule Class extends the Rule Class.
|
||||
*
|
||||
* At present it does not add any new methods or attributes
|
||||
* @type {ProtocolMatchingRule}
|
||||
*/
|
||||
HP.ProtocolMatchingRule = class ProtocolMatchingRule extends Rule {};
|
||||
@ -0,0 +1,9 @@
|
||||
import { Rule } from '../Rule';
|
||||
|
||||
/**
|
||||
* The SeriesMatchingRule Class extends the Rule Class.
|
||||
*
|
||||
* At present it does not add any new methods or attributes
|
||||
* @type {SeriesMatchingRule}
|
||||
*/
|
||||
HP.SeriesMatchingRule = class SeriesMatchingRule extends Rule {};
|
||||
@ -0,0 +1,9 @@
|
||||
import { Rule } from '../Rule';
|
||||
|
||||
/**
|
||||
* The StudyMatchingRule Class extends the Rule Class.
|
||||
*
|
||||
* At present it does not add any new methods or attributes
|
||||
* @type {StudyMatchingRule}
|
||||
*/
|
||||
HP.StudyMatchingRule = class StudyMatchingRule extends Rule {};
|
||||
72
Packages/ohif-hanging-protocols/both/lib/comparators.js
Normal file
72
Packages/ohif-hanging-protocols/both/lib/comparators.js
Normal file
@ -0,0 +1,72 @@
|
||||
const comparators = [{
|
||||
id: 'equals',
|
||||
name: '= (Equals)',
|
||||
validator: 'equals',
|
||||
validatorOption: 'value',
|
||||
description: 'The attribute must equal this value.'
|
||||
}, {
|
||||
id: 'doesNotEqual',
|
||||
name: '!= (Does not equal)',
|
||||
validator: 'doesNotEqual',
|
||||
validatorOption: 'value',
|
||||
description: 'The attribute must not equal this value.'
|
||||
}, {
|
||||
id: 'contains',
|
||||
name: 'Contains',
|
||||
validator: 'contains',
|
||||
validatorOption: 'value',
|
||||
description: 'The attribute must contain this value.'
|
||||
}, {
|
||||
id: 'doesNotContain',
|
||||
name: 'Does not contain',
|
||||
validator: 'doesNotContain',
|
||||
validatorOption: 'value',
|
||||
description: 'The attribute must not contain this value.'
|
||||
}, {
|
||||
id: 'onlyInteger',
|
||||
name: 'Only Integers',
|
||||
validator: 'numericality',
|
||||
validatorOption: 'onlyInteger',
|
||||
description: "Real numbers won't be allowed."
|
||||
}, {
|
||||
id: 'greaterThan',
|
||||
name: '> (Greater than)',
|
||||
validator: 'numericality',
|
||||
validatorOption: 'greaterThan',
|
||||
description: 'The attribute has to be greater than this value.'
|
||||
}, {
|
||||
id: 'greaterThanOrEqualTo',
|
||||
name: '>= (Greater than or equal to)',
|
||||
validator: 'numericality',
|
||||
validatorOption: 'greaterThanOrEqualTo',
|
||||
description: 'The attribute has to be at least this value.'
|
||||
}, {
|
||||
id: 'lessThanOrEqualTo',
|
||||
name: '<= (Less than or equal to)',
|
||||
validator: 'numericality',
|
||||
validatorOption: 'lessThanOrEqualTo',
|
||||
description: 'The attribute can be this value at the most.'
|
||||
}, {
|
||||
id: 'lessThan',
|
||||
name: '< (Less than)',
|
||||
validator: 'numericality',
|
||||
validatorOption: 'lessThan',
|
||||
description: 'The attribute has to be less than this value.'
|
||||
}, {
|
||||
id: 'odd',
|
||||
name: 'Odd',
|
||||
validator: 'numericality',
|
||||
validatorOption: 'odd',
|
||||
description: 'The attribute has to be odd.'
|
||||
}, {
|
||||
id: 'even',
|
||||
name: 'Even',
|
||||
validator: 'numericality',
|
||||
validatorOption: 'even',
|
||||
description: 'The attribute has to be even.'
|
||||
}];
|
||||
|
||||
// Immutable object
|
||||
Object.freeze(comparators);
|
||||
|
||||
export { comparators }
|
||||
33
Packages/ohif-hanging-protocols/both/lib/removeFromArray.js
Normal file
33
Packages/ohif-hanging-protocols/both/lib/removeFromArray.js
Normal file
@ -0,0 +1,33 @@
|
||||
import { _ } from 'meteor/underscore';
|
||||
|
||||
/**
|
||||
* Removes the first instance of an element from an array, if an equal value exists
|
||||
*
|
||||
* @param array
|
||||
* @param input
|
||||
*
|
||||
* @returns {boolean} Whether or not the element was found and removed
|
||||
*/
|
||||
const removeFromArray = (array, input) => {
|
||||
// If the array is empty, stop here
|
||||
if (!array ||
|
||||
!array.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
array.forEach((value, index) => {
|
||||
if (_.isEqual(value, input)) {
|
||||
indexToRemove = index;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (indexToRemove === void 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
array.splice(indexToRemove, 1);
|
||||
return true;
|
||||
};
|
||||
|
||||
export { removeFromArray };
|
||||
@ -1,589 +1,13 @@
|
||||
/**
|
||||
* Removes the first instance of an element from an array, if an equal value exists
|
||||
*
|
||||
* @param array
|
||||
* @param input
|
||||
*
|
||||
* @returns {boolean} Whether or not the element was found and removed
|
||||
*/
|
||||
function removeFromArray(array, input) {
|
||||
// If the array is empty, stop here
|
||||
if (!array ||
|
||||
!array.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
array.forEach(function(value, index) {
|
||||
if (_.isEqual(value, input)) {
|
||||
indexToRemove = index;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (indexToRemove === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
array.splice(indexToRemove, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This class represents a Hanging Protocol at the highest level
|
||||
*
|
||||
* @type {Protocol}
|
||||
*/
|
||||
HP.Protocol = class Protocol {
|
||||
/**
|
||||
* The Constructor for the Class to create a Protocol with the bare
|
||||
* minimum information
|
||||
*
|
||||
* @param name The desired name for the Protocol
|
||||
*/
|
||||
constructor(name) {
|
||||
// Create a new UUID for this Protocol
|
||||
this.id = Random.id();
|
||||
|
||||
// Store a value which determines whether or not a Protocol is locked
|
||||
// This is probably temporary, since we will eventually have role / user
|
||||
// checks for editing. For now we just need it to prevent changes to the
|
||||
// default protocols.
|
||||
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
|
||||
this.name = name;
|
||||
|
||||
// Set the created and modified dates to Now
|
||||
this.createdDate = new Date();
|
||||
this.modifiedDate = new Date();
|
||||
|
||||
// If we are logged in while creating this Protocol,
|
||||
// store this information as well
|
||||
if (Meteor.users && Meteor.userId) {
|
||||
this.createdBy = Meteor.userId;
|
||||
this.modifiedBy = Meteor.userId;
|
||||
}
|
||||
|
||||
// Create two empty Sets specifying which roles
|
||||
// have read and write access to this Protocol
|
||||
this.availableTo = new Set();
|
||||
this.editableBy = new Set();
|
||||
|
||||
// Define empty arrays for the Protocol matching rules
|
||||
// and Stages
|
||||
this.protocolMatchingRules = [];
|
||||
this.stages = [];
|
||||
|
||||
// Define auxiliary values for priors
|
||||
this.numberOfPriorsReferenced = 0;
|
||||
this.numberOfPriorsReferencedRequired = 0;
|
||||
}
|
||||
|
||||
updateNumberOfPriorsReferenced() {
|
||||
let numPriorsReferenced = 0;
|
||||
let numberOfPriorsReferencedRequired = 0;
|
||||
|
||||
this.stages.forEach(stage => {
|
||||
if (!stage.viewports) {
|
||||
return;
|
||||
}
|
||||
|
||||
stage.viewports.forEach(viewport => {
|
||||
if (!viewport.studyMatchingRules) {
|
||||
return;
|
||||
}
|
||||
|
||||
viewport.studyMatchingRules.forEach(rule => {
|
||||
if (rule.attribute === 'abstractPriorValue') {
|
||||
// If the rule is required
|
||||
if (rule.required) {
|
||||
numberOfPriorsReferencedRequired++;
|
||||
}
|
||||
|
||||
// TODO: Double check here that the abstractPriorValue is not
|
||||
// set as zero
|
||||
numPriorsReferenced++;
|
||||
} else if (rule.attribute === 'relativeTime') {
|
||||
// If the rule is required
|
||||
if (rule.required) {
|
||||
numberOfPriorsReferencedRequired++;
|
||||
}
|
||||
|
||||
numPriorsReferenced++;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
this.numberOfPriorsReferenced = numPriorsReferenced;
|
||||
this.numberOfPriorsReferencedRequired = numberOfPriorsReferencedRequired;
|
||||
|
||||
// To indicate that the priors informations were updated
|
||||
this.hasUpdatedPriorsInformation = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to update the modifiedDate when the Protocol
|
||||
* has been changed
|
||||
*/
|
||||
protocolWasModified() {
|
||||
// If we are logged in while modifying this Protocol,
|
||||
// store this information as well
|
||||
if (Meteor.users && 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();
|
||||
|
||||
// Update the modifiedDate with the current Date/Time
|
||||
this.modifiedDate = new Date();
|
||||
}
|
||||
|
||||
/**
|
||||
* Occasionally the Protocol class needs to be instantiated from a JavaScript Object
|
||||
* containing the Protocol data. This function fills in a Protocol with the Object
|
||||
* data.
|
||||
*
|
||||
* @param input A Protocol as a JavaScript Object, e.g. retrieved from MongoDB or JSON
|
||||
*/
|
||||
fromObject(input) {
|
||||
// Check if the input already has an ID
|
||||
// If so, keep it. It not, create a new UUID
|
||||
this.id = input.id || Random.id();
|
||||
|
||||
// Assign the input name to the Protocol
|
||||
this.name = input.name;
|
||||
|
||||
// Retrieve locked status, use !! to make it truthy
|
||||
// so that undefined values will be set to false
|
||||
this.locked = !!input.locked;
|
||||
|
||||
// TODO: Check how to regenerate Set from Object
|
||||
//this.availableTo = new Set(input.availableTo);
|
||||
//this.editableBy = new Set(input.editableBy);
|
||||
|
||||
// If the input contains Protocol matching rules
|
||||
if (input.protocolMatchingRules) {
|
||||
input.protocolMatchingRules.forEach(ruleObject => {
|
||||
// Create new Rules from the stored data
|
||||
var rule = new HP.ProtocolMatchingRule();
|
||||
rule.fromObject(ruleObject);
|
||||
|
||||
// Add them to the Protocol
|
||||
this.protocolMatchingRules.push(rule);
|
||||
});
|
||||
}
|
||||
|
||||
// If the input contains data for various Stages in the
|
||||
// display set sequence
|
||||
if (input.stages) {
|
||||
input.stages.forEach(stageObject => {
|
||||
// Create Stages from the stored data
|
||||
var stage = new HP.Stage();
|
||||
stage.fromObject(stageObject);
|
||||
|
||||
// Add them to the Protocol
|
||||
this.stages.push(stage);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a clone of the current Protocol with a new name
|
||||
*
|
||||
* @param name
|
||||
* @returns {Protocol|*}
|
||||
*/
|
||||
createClone(name) {
|
||||
// Create a new JavaScript independent of the current Protocol
|
||||
var currentProtocol = $.extend({}, this);
|
||||
|
||||
// Create a new Protocol to return
|
||||
var clonedProtocol = new HP.Protocol();
|
||||
|
||||
// Apply the desired properties
|
||||
currentProtocol.id = clonedProtocol.id;
|
||||
clonedProtocol.fromObject(currentProtocol);
|
||||
|
||||
// If we have specified a name, assign it
|
||||
if (name) {
|
||||
clonedProtocol.name = name;
|
||||
}
|
||||
|
||||
// Unlock the clone
|
||||
clonedProtocol.locked = false;
|
||||
|
||||
// Return the cloned Protocol
|
||||
return clonedProtocol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a Stage to this Protocol's display set sequence
|
||||
*
|
||||
* @param stage
|
||||
*/
|
||||
addStage(stage) {
|
||||
this.stages.push(stage);
|
||||
|
||||
// Update the modifiedDate and User that last
|
||||
// modified this Protocol
|
||||
this.protocolWasModified();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a Rule to this Protocol's array of matching rules
|
||||
*
|
||||
* @param rule
|
||||
*/
|
||||
addProtocolMatchingRule(rule) {
|
||||
this.protocolMatchingRules.push(rule);
|
||||
|
||||
// Update the modifiedDate and User that last
|
||||
// modified this Protocol
|
||||
this.protocolWasModified();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a Rule from this Protocol's array of matching rules
|
||||
*
|
||||
* @param rule
|
||||
*/
|
||||
removeProtocolMatchingRule(rule) {
|
||||
var wasRemoved = removeFromArray(this.protocolMatchingRules, rule);
|
||||
|
||||
// Update the modifiedDate and User that last
|
||||
// modified this Protocol
|
||||
if (wasRemoved) {
|
||||
this.protocolWasModified();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This Class represents a Rule to be evaluated given a set of attributes
|
||||
* Rules have:
|
||||
* - An attribute (e.g. 'seriesDescription')
|
||||
* - A constraint Object, in the form required by Validate.js:
|
||||
*
|
||||
* rule.constraint = {
|
||||
* contains: {
|
||||
* value: 'T-1'
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* Note: In this example we use the 'contains' Validator, which is a custom Validator defined in Viewerbase
|
||||
*
|
||||
* - A value for whether or not they are Required to be matched (default: False)
|
||||
* - A value for their relative weighting during Protocol or Image matching (default: 1)
|
||||
*/
|
||||
class Rule {
|
||||
/**
|
||||
* The Constructor for the Class to create a Rule with the bare
|
||||
* minimum information
|
||||
*
|
||||
* @param name The desired name for the Rule
|
||||
*/
|
||||
constructor(attribute, constraint, required, weight) {
|
||||
// Create a new UUID for this Rule
|
||||
this.id = Random.id();
|
||||
|
||||
// Set the Rule's weight (defaults to 1)
|
||||
this.weight = weight || 1;
|
||||
|
||||
// If an attribute is specified, assign it
|
||||
if (attribute) {
|
||||
this.attribute = attribute;
|
||||
}
|
||||
|
||||
// If a constraint is specified, assign it
|
||||
if (constraint) {
|
||||
this.constraint = constraint;
|
||||
}
|
||||
|
||||
// If a value for 'required' is specified, assign it
|
||||
if (required === undefined) {
|
||||
// If no value was specified, default to False
|
||||
this.required = false;
|
||||
} else {
|
||||
this.required = required;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Occasionally the Rule class needs to be instantiated from a JavaScript Object.
|
||||
* This function fills in a Protocol with the Object data.
|
||||
*
|
||||
* @param input A Rule as a JavaScript Object, e.g. retrieved from MongoDB or JSON
|
||||
*/
|
||||
fromObject(input) {
|
||||
// Check if the input already has an ID
|
||||
// If so, keep it. It not, create a new UUID
|
||||
this.id = input.id || Random.id();
|
||||
|
||||
// Assign the specified input data to the Rule
|
||||
this.required = input.required;
|
||||
this.weight = input.weight;
|
||||
this.attribute = input.attribute;
|
||||
this.constraint = input.constraint;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The ProtocolMatchingRule Class extends the Rule Class.
|
||||
*
|
||||
* At present it does not add any new methods or attributes
|
||||
* @type {ProtocolMatchingRule}
|
||||
*/
|
||||
HP.ProtocolMatchingRule = class ProtocolMatchingRule extends Rule {};
|
||||
|
||||
/**
|
||||
* A Stage is one step in the Display Set Sequence for a Hanging Protocol
|
||||
*
|
||||
* Stages are defined as a ViewportStructure and an array of Viewports
|
||||
*
|
||||
* @type {Stage}
|
||||
*/
|
||||
HP.Stage = class Stage {
|
||||
constructor(ViewportStructure, name) {
|
||||
// Create a new UUID for this Stage
|
||||
this.id = Random.id();
|
||||
|
||||
// Assign the name and ViewportStructure provided
|
||||
this.name = name;
|
||||
this.viewportStructure = ViewportStructure;
|
||||
|
||||
// Create an empty array for the Viewports
|
||||
this.viewports = [];
|
||||
|
||||
// Set the created date to Now
|
||||
this.createdDate = new Date();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a clone of the current Stage with a new name
|
||||
*
|
||||
* Note! This method absolutely cannot be renamed 'clone', because
|
||||
* Minimongo's insert method uses 'clone' internally and this
|
||||
* somehow causes very bizarre behaviour
|
||||
*
|
||||
* @param name
|
||||
* @returns {Stage|*}
|
||||
*/
|
||||
createClone(name) {
|
||||
// Create a new JavaScript independent of the current Protocol
|
||||
var currentStage = $.extend({}, this);
|
||||
|
||||
// Create a new Stage to return
|
||||
var clonedStage = new HP.Stage();
|
||||
|
||||
// Assign the desired properties
|
||||
currentStage.id = clonedStage.id;
|
||||
clonedStage.fromObject(currentStage);
|
||||
|
||||
// If we have specified a name, assign it
|
||||
if (name) {
|
||||
clonedStage.name = name;
|
||||
}
|
||||
|
||||
// Return the cloned Stage
|
||||
return clonedStage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Occasionally the Stage class needs to be instantiated from a JavaScript Object.
|
||||
* This function fills in a Protocol with the Object data.
|
||||
*
|
||||
* @param input A Stage as a JavaScript Object, e.g. retrieved from MongoDB or JSON
|
||||
*/
|
||||
fromObject(input) {
|
||||
// Check if the input already has an ID
|
||||
// If so, keep it. It not, create a new UUID
|
||||
this.id = input.id || Random.id();
|
||||
|
||||
// Assign the input name to the Stage
|
||||
this.name = input.name;
|
||||
|
||||
// If a ViewportStructure is present in the input, add it from the
|
||||
// input data
|
||||
this.viewportStructure = new HP.ViewportStructure();
|
||||
this.viewportStructure.fromObject(input.viewportStructure);
|
||||
|
||||
// If any viewports are present in the input object
|
||||
if (input.viewports) {
|
||||
input.viewports.forEach(viewportObject => {
|
||||
// Create a new Viewport with their data
|
||||
var viewport = new HP.Viewport();
|
||||
viewport.fromObject(viewportObject);
|
||||
|
||||
// Add it to the viewports array
|
||||
this.viewports.push(viewport);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The ViewportStructure class represents the layout and layout properties that
|
||||
* Viewports are displayed in. ViewportStructure has a type, which corresponds to
|
||||
* a layout template, and a set of properties, which depend on the type.
|
||||
*
|
||||
* @type {ViewportStructure}
|
||||
*/
|
||||
HP.ViewportStructure = class ViewportStructure {
|
||||
constructor(type, properties) {
|
||||
this.type = type;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Occasionally the ViewportStructure class needs to be instantiated from a JavaScript Object.
|
||||
* This function fills in a ViewportStructure with the Object data.
|
||||
*
|
||||
* @param input The ViewportStructure as a JavaScript Object, e.g. retrieved from MongoDB or JSON
|
||||
*/
|
||||
fromObject(input) {
|
||||
this.type = input.type;
|
||||
this.properties = input.properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the layout template name based on the layout type
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
getLayoutTemplateName() {
|
||||
// Viewport structure can be updated later when we build more complex display layouts
|
||||
switch (this.type) {
|
||||
case 'grid':
|
||||
return 'gridLayout';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the number of Viewports required for this layout
|
||||
* given the layout type and properties
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
getNumViewports() {
|
||||
// Viewport structure can be updated later when we build more complex display layouts
|
||||
switch (this.type) {
|
||||
case 'grid':
|
||||
// For the typical grid layout, we only need to multiply rows by columns to
|
||||
// obtain the number of viewports
|
||||
return this.properties.rows * this.properties.columns;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This Class defines a Viewport in the Hanging Protocol Stage. A Viewport contains
|
||||
* arrays of Rules that are matched in the ProtocolEngine in order to determine which
|
||||
* images should be hung.
|
||||
*
|
||||
* @type {Viewport}
|
||||
*/
|
||||
HP.Viewport = class Viewport {
|
||||
constructor() {
|
||||
this.viewportSettings = {};
|
||||
this.imageMatchingRules = [];
|
||||
this.seriesMatchingRules = [];
|
||||
this.studyMatchingRules = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Occasionally the Viewport class needs to be instantiated from a JavaScript Object.
|
||||
* This function fills in a Viewport with the Object data.
|
||||
*
|
||||
* @param input The Viewport as a JavaScript Object, e.g. retrieved from MongoDB or JSON
|
||||
*/
|
||||
fromObject(input) {
|
||||
// If ImageMatchingRules exist, create them from the Object data
|
||||
// and add them to the Viewport's imageMatchingRules array
|
||||
if (input.imageMatchingRules) {
|
||||
input.imageMatchingRules.forEach(ruleObject => {
|
||||
var rule = new HP.ImageMatchingRule();
|
||||
rule.fromObject(ruleObject);
|
||||
this.imageMatchingRules.push(rule);
|
||||
});
|
||||
}
|
||||
|
||||
// If SeriesMatchingRules exist, create them from the Object data
|
||||
// and add them to the Viewport's seriesMatchingRules array
|
||||
if (input.seriesMatchingRules) {
|
||||
input.seriesMatchingRules.forEach(ruleObject => {
|
||||
var rule = new HP.SeriesMatchingRule();
|
||||
rule.fromObject(ruleObject);
|
||||
this.seriesMatchingRules.push(rule);
|
||||
});
|
||||
}
|
||||
|
||||
// If StudyMatchingRules exist, create them from the Object data
|
||||
// and add them to the Viewport's studyMatchingRules array
|
||||
if (input.studyMatchingRules) {
|
||||
input.studyMatchingRules.forEach(ruleObject => {
|
||||
var rule = new HP.StudyMatchingRule();
|
||||
rule.fromObject(ruleObject);
|
||||
this.studyMatchingRules.push(rule);
|
||||
});
|
||||
}
|
||||
|
||||
// If ViewportSettings exist, add them to the current protocol
|
||||
if (input.viewportSettings) {
|
||||
this.viewportSettings = input.viewportSettings;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and removes a rule from whichever array it exists in.
|
||||
* It is not required to specify if it exists in studyMatchingRules,
|
||||
* seriesMatchingRules, or imageMatchingRules
|
||||
*
|
||||
* @param rule
|
||||
*/
|
||||
removeRule(rule) {
|
||||
var array;
|
||||
if (rule instanceof HP.StudyMatchingRule) {
|
||||
array = this.studyMatchingRules;
|
||||
} else if (rule instanceof HP.SeriesMatchingRule) {
|
||||
array = this.seriesMatchingRules;
|
||||
} else if (rule instanceof HP.ImageMatchingRule) {
|
||||
array = this.imageMatchingRules;
|
||||
}
|
||||
|
||||
removeFromArray(array, rule);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The ImageMatchingRule class extends the Rule Class.
|
||||
*
|
||||
* At present it does not add any new methods or attributes
|
||||
* @type {ImageMatchingRule}
|
||||
*/
|
||||
HP.ImageMatchingRule = class ImageMatchingRule extends Rule {};
|
||||
|
||||
/**
|
||||
* The SeriesMatchingRule Class extends the Rule Class.
|
||||
*
|
||||
* At present it does not add any new methods or attributes
|
||||
* @type {SeriesMatchingRule}
|
||||
*/
|
||||
HP.SeriesMatchingRule = class SeriesMatchingRule extends Rule {};
|
||||
|
||||
/**
|
||||
* The StudyMatchingRule Class extends the Rule Class.
|
||||
*
|
||||
* At present it does not add any new methods or attributes
|
||||
* @type {StudyMatchingRule}
|
||||
*/
|
||||
HP.StudyMatchingRule = class StudyMatchingRule extends Rule {};
|
||||
// @TODO start using namespace instead
|
||||
|
||||
// Base classes
|
||||
import './classes/Protocol';
|
||||
import './classes/Stage';
|
||||
import './classes/Viewport';
|
||||
import './classes/ViewportStructure';
|
||||
|
||||
// Specialized Rule classes
|
||||
import './classes/rules/ProtocolMatchingRule';
|
||||
import './classes/rules/StudyMatchingRule';
|
||||
import './classes/rules/SeriesMatchingRule';
|
||||
import './classes/rules/ImageMatchingRule';
|
||||
|
||||
@ -416,7 +416,7 @@ function getDemoProtocols() {
|
||||
"viewportSettings": {},
|
||||
"imageMatchingRules": [],
|
||||
"seriesMatchingRules": [{
|
||||
"id": "mXnsCcNzZL56z7mTZ",
|
||||
"id": "mXnsCcNzZL56z7mac",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "x0008103e",
|
||||
@ -431,7 +431,7 @@ function getDemoProtocols() {
|
||||
"viewportSettings": {},
|
||||
"imageMatchingRules": [],
|
||||
"seriesMatchingRules": [{
|
||||
"id": "ygz4nb28iJZcJhnYa",
|
||||
"id": "ygz4nb28iJZcJhnYc",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "x0008103e",
|
||||
@ -442,7 +442,7 @@ function getDemoProtocols() {
|
||||
}
|
||||
}],
|
||||
"studyMatchingRules": [{
|
||||
"id": "uDoEgLTvnXTByWnPz",
|
||||
"id": "uDoEgLTvnXTByWnPt",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "abstractPriorValue",
|
||||
@ -540,7 +540,7 @@ function getDemoProtocols() {
|
||||
"viewportSettings": {},
|
||||
"imageMatchingRules": [],
|
||||
"seriesMatchingRules": [{
|
||||
"id": "mXnsCcNzZL56z7mTZ",
|
||||
"id": "mXnsCcNzZL56z7mtr",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "x0008103e",
|
||||
@ -567,7 +567,7 @@ function getDemoProtocols() {
|
||||
},
|
||||
"imageMatchingRules": [],
|
||||
"seriesMatchingRules": [{
|
||||
"id": "ygz4nb28iJZcJhnYa",
|
||||
"id": "ygz4nb28iJZcJhnYb",
|
||||
"weight": 2,
|
||||
"required": false,
|
||||
"attribute": "x0008103e",
|
||||
@ -704,7 +704,7 @@ function getDemoProtocols() {
|
||||
"viewportSettings": {},
|
||||
"imageMatchingRules": [],
|
||||
"seriesMatchingRules": [{
|
||||
"id": "mXnsCcNzZL56z7mTZ",
|
||||
"id": "mXnsCcNzZL55z7mTZ",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "x0008103e",
|
||||
@ -733,7 +733,7 @@ function getDemoProtocols() {
|
||||
"viewportSettings": {},
|
||||
"imageMatchingRules": [],
|
||||
"seriesMatchingRules": [{
|
||||
"id": "mXnsCcNzZL56z7mTZ",
|
||||
"id": "mXnsCcNzZL56z7nTZ",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "x0008103e",
|
||||
@ -743,7 +743,7 @@ function getDemoProtocols() {
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"id": "mXnsCcNzZL56z7mTZ",
|
||||
"id": "mXnsCcNzZL56z7rTZ",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "x0008103e",
|
||||
@ -758,7 +758,7 @@ function getDemoProtocols() {
|
||||
"viewportSettings": {},
|
||||
"imageMatchingRules": [],
|
||||
"seriesMatchingRules": [{
|
||||
"id": "mXnsCcNzZL56z7mTZ",
|
||||
"id": "mXnsCcNzZL56r7mTZ",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "x0008103e",
|
||||
@ -768,7 +768,7 @@ function getDemoProtocols() {
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"id": "mXnsCcNzZL56z7mTZ",
|
||||
"id": "mXnsCcNzZL56a7mTZ",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "x0008103e",
|
||||
@ -783,7 +783,7 @@ function getDemoProtocols() {
|
||||
"viewportSettings": {},
|
||||
"imageMatchingRules": [],
|
||||
"seriesMatchingRules": [{
|
||||
"id": "mXnsCcNzZL56z7mTZ",
|
||||
"id": "mXnsCcRzZL56z7mTZ",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "x0008103e",
|
||||
@ -793,7 +793,7 @@ function getDemoProtocols() {
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"id": "mXnsCcNzZL56z7mTZ",
|
||||
"id": "mXnsCcNzTL56z7mTZ",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "x0008103e",
|
||||
@ -808,7 +808,7 @@ function getDemoProtocols() {
|
||||
"viewportSettings": {},
|
||||
"imageMatchingRules": [],
|
||||
"seriesMatchingRules": [{
|
||||
"id": "mXnsCcNzZL56z7mTZ",
|
||||
"id": "mXnsCcMzZL56z7mTZ",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "x0008103e",
|
||||
@ -818,7 +818,7 @@ function getDemoProtocols() {
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"id": "mXnsCcNzZL56z7mTZ",
|
||||
"id": "mXnsCcAzZL56z7mTZ",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "x0008103e",
|
||||
@ -873,7 +873,7 @@ function getDemoProtocols() {
|
||||
"viewportSettings": {},
|
||||
"imageMatchingRules": [],
|
||||
"seriesMatchingRules": [{
|
||||
"id": "mXnsCcNzZL56z7mTZ",
|
||||
"id": "mXnsCcAzZL56z7mTZ",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "x0008103e",
|
||||
@ -888,7 +888,7 @@ function getDemoProtocols() {
|
||||
"viewportSettings": {},
|
||||
"imageMatchingRules": [],
|
||||
"seriesMatchingRules": [{
|
||||
"id": "mXnsCcNzZL56z7mTZ",
|
||||
"id": "mXnsCcNzZR56z7mTZ",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "x0008103e",
|
||||
@ -898,7 +898,7 @@ function getDemoProtocols() {
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"id": "mXnsCcNzZL56z7mTZ",
|
||||
"id": "mRnsCcNzZL56z7mTZ",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "x00200011",
|
||||
@ -926,7 +926,7 @@ function getDemoProtocols() {
|
||||
"viewportSettings": {},
|
||||
"imageMatchingRules": [],
|
||||
"seriesMatchingRules": [{
|
||||
"id": "mXnsCcNzZL56z7mTZ",
|
||||
"id": "mXnsGcNzZL56z7mTZ",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "x0008103e",
|
||||
@ -941,7 +941,7 @@ function getDemoProtocols() {
|
||||
"viewportSettings": {},
|
||||
"imageMatchingRules": [],
|
||||
"seriesMatchingRules": [{
|
||||
"id": "mXnsCcNzZL56z7mTZ",
|
||||
"id": "mXnsHcNzZL56z7mTZ",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "x0008103e",
|
||||
@ -971,7 +971,7 @@ function getDemoProtocols() {
|
||||
},
|
||||
"imageMatchingRules": [],
|
||||
"seriesMatchingRules": [{
|
||||
"id": "mXnsCcNzZL56z7mTZ",
|
||||
"id": "mXneCcNzZL56z7mTZ",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "x0008103e",
|
||||
@ -986,7 +986,7 @@ function getDemoProtocols() {
|
||||
"viewportSettings": {},
|
||||
"imageMatchingRules": [],
|
||||
"seriesMatchingRules": [{
|
||||
"id": "mXnsCcNzZL56z7mTZ",
|
||||
"id": "mXnsCuNzZL56z7mTZ",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "x0008103e",
|
||||
@ -1041,7 +1041,7 @@ function getDemoProtocols() {
|
||||
"viewportSettings": {},
|
||||
"imageMatchingRules": [],
|
||||
"seriesMatchingRules": [{
|
||||
"id": "mXnsCcNzZL56z7mTZ",
|
||||
"id": "mXnsCcNzZL59z7mTZ",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "x0008103e",
|
||||
@ -1056,7 +1056,7 @@ function getDemoProtocols() {
|
||||
"viewportSettings": {},
|
||||
"imageMatchingRules": [],
|
||||
"seriesMatchingRules": [{
|
||||
"id": "mXnsCcNzZL56z7mTZ",
|
||||
"id": "mXnsCcNzZL56z7lTZ",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "x0008103e",
|
||||
@ -1067,7 +1067,7 @@ function getDemoProtocols() {
|
||||
}
|
||||
}],
|
||||
"studyMatchingRules": [{
|
||||
"id": "uDoEgLTvnXTByWnPz",
|
||||
"id": "uDoEgLTbnXTByWnPz",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "abstractPriorValue",
|
||||
@ -1094,7 +1094,7 @@ function getDemoProtocols() {
|
||||
"viewportSettings": {},
|
||||
"imageMatchingRules": [],
|
||||
"seriesMatchingRules": [{
|
||||
"id": "mXnsCcNzZL56z7mTZ",
|
||||
"id": "mXnsCcNjZL56z7mTZ",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "x0008103e",
|
||||
@ -1104,7 +1104,7 @@ function getDemoProtocols() {
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"id": "mXnsCcNzZL56z7mTZ",
|
||||
"id": "mXnsCcNzZL56z7gTZ",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "x00200011",
|
||||
@ -1119,7 +1119,7 @@ function getDemoProtocols() {
|
||||
"viewportSettings": {},
|
||||
"imageMatchingRules": [],
|
||||
"seriesMatchingRules": [{
|
||||
"id": "mXnsCcNzZL56z7mTZ",
|
||||
"id": "mXnsCcCzZL56z7mTZ",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "x0008103e",
|
||||
@ -1140,7 +1140,7 @@ function getDemoProtocols() {
|
||||
}
|
||||
}],
|
||||
"studyMatchingRules": [{
|
||||
"id": "uDoEgLTvnXTByWnPz",
|
||||
"id": "uDoEgLTvn1TByWnPz",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "abstractPriorValue",
|
||||
@ -1167,7 +1167,7 @@ function getDemoProtocols() {
|
||||
"viewportSettings": {},
|
||||
"imageMatchingRules": [],
|
||||
"seriesMatchingRules": [{
|
||||
"id": "mXnsCcNzZL56z7mTZ",
|
||||
"id": "mXnsCcNzZL26z7mTZ",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "x0008103e",
|
||||
@ -1182,7 +1182,7 @@ function getDemoProtocols() {
|
||||
"viewportSettings": {},
|
||||
"imageMatchingRules": [],
|
||||
"seriesMatchingRules": [{
|
||||
"id": "mXnsCcNzZL56z7mTZ",
|
||||
"id": "mXnsCcNzZL46z7mTZ",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "x0008103e",
|
||||
@ -1197,7 +1197,7 @@ function getDemoProtocols() {
|
||||
"viewportSettings": {},
|
||||
"imageMatchingRules": [],
|
||||
"seriesMatchingRules": [{
|
||||
"id": "mXnsCcNzZL56z7mTZ",
|
||||
"id": "mXnsCcNzZL57z7mTZ",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "x0008103e",
|
||||
@ -1208,7 +1208,7 @@ function getDemoProtocols() {
|
||||
}
|
||||
}],
|
||||
"studyMatchingRules": [{
|
||||
"id": "uDoEgLTvnXTByWnPz",
|
||||
"id": "uDoEgLTvnYTByWnPz",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "abstractPriorValue",
|
||||
@ -1222,7 +1222,7 @@ function getDemoProtocols() {
|
||||
"viewportSettings": {},
|
||||
"imageMatchingRules": [],
|
||||
"seriesMatchingRules": [{
|
||||
"id": "mXnsCcNzZL56z7mTZ",
|
||||
"id": "mXnsCcNzZQ56z7mTZ",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "x0008103e",
|
||||
@ -1233,7 +1233,7 @@ function getDemoProtocols() {
|
||||
}
|
||||
}],
|
||||
"studyMatchingRules": [{
|
||||
"id": "uDoEgLTvnXTByWnPz",
|
||||
"id": "uDoEgLTvnKTByWnPz",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "abstractPriorValue",
|
||||
@ -1262,7 +1262,7 @@ function getDemoProtocols() {
|
||||
},
|
||||
"imageMatchingRules": [],
|
||||
"seriesMatchingRules": [{
|
||||
"id": "mXnsCcNzZL56z7mTZ",
|
||||
"id": "mXnsCcNzZL56z7nTZ",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "x0008103e",
|
||||
@ -1277,7 +1277,7 @@ function getDemoProtocols() {
|
||||
"viewportSettings": {},
|
||||
"imageMatchingRules": [],
|
||||
"seriesMatchingRules": [{
|
||||
"id": "mXnsCcNzZL56z7mTZ",
|
||||
"id": "mXnsCcNxZL56z7mTZ",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "x0008103e",
|
||||
@ -1294,7 +1294,7 @@ function getDemoProtocols() {
|
||||
},
|
||||
"imageMatchingRules": [],
|
||||
"seriesMatchingRules": [{
|
||||
"id": "mXnsCcNzZL56z7mTZ",
|
||||
"id": "mXnsCcNzZA56z7mTZ",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "x0008103e",
|
||||
@ -1305,7 +1305,7 @@ function getDemoProtocols() {
|
||||
}
|
||||
}],
|
||||
"studyMatchingRules": [{
|
||||
"id": "uDoEgLTvnXTByWnPz",
|
||||
"id": "uDoEgHTvnXTByWnPz",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "abstractPriorValue",
|
||||
@ -1319,7 +1319,7 @@ function getDemoProtocols() {
|
||||
"viewportSettings": {},
|
||||
"imageMatchingRules": [],
|
||||
"seriesMatchingRules": [{
|
||||
"id": "mXnsCcNzZL56z7mTZ",
|
||||
"id": "mXnsCcNzZP56z7mTZ",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "x0008103e",
|
||||
@ -1330,7 +1330,7 @@ function getDemoProtocols() {
|
||||
}
|
||||
}],
|
||||
"studyMatchingRules": [{
|
||||
"id": "uDoEgLTvnXTByWnPz",
|
||||
"id": "uDoEgITvnXTByWnPz",
|
||||
"weight": 1,
|
||||
"required": false,
|
||||
"attribute": "abstractPriorValue",
|
||||
|
||||
@ -1,77 +1,12 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { comparators } from '../both/lib/comparators';
|
||||
|
||||
MatchedProtocols = new Meteor.Collection(null);
|
||||
MatchedProtocols._debugName = 'MatchedProtocols';
|
||||
|
||||
Comparators = new Meteor.Collection(null);
|
||||
Comparators._debugName = 'Comparators';
|
||||
|
||||
var comparators = [{
|
||||
id: 'equals',
|
||||
name: '= (Equals)',
|
||||
validator: 'equals',
|
||||
validatorOption: 'value',
|
||||
description: 'The attribute must equal this value.'
|
||||
}, {
|
||||
id: 'doesNotEqual',
|
||||
name: '!= (Does not equal)',
|
||||
validator: 'doesNotEqual',
|
||||
validatorOption: 'value',
|
||||
description: 'The attribute must not equal this value.'
|
||||
}, {
|
||||
id: 'contains',
|
||||
name: 'Contains',
|
||||
validator: 'contains',
|
||||
validatorOption: 'value',
|
||||
description: 'The attribute must contain this value.'
|
||||
}, {
|
||||
id: 'doesNotContain',
|
||||
name: 'Does not contain',
|
||||
validator: 'doesNotContain',
|
||||
validatorOption: 'value',
|
||||
description: 'The attribute must not contain this value.'
|
||||
}, {
|
||||
id: 'onlyInteger',
|
||||
name: 'Only Integers',
|
||||
validator: 'numericality',
|
||||
validatorOption: 'onlyInteger',
|
||||
description: "Real numbers won't be allowed."
|
||||
}, {
|
||||
id: 'greaterThan',
|
||||
name: '> (Greater than)',
|
||||
validator: 'numericality',
|
||||
validatorOption: 'greaterThan',
|
||||
description: 'The attribute has to be greater than this value.'
|
||||
}, {
|
||||
id: 'greaterThanOrEqualTo',
|
||||
name: '>= (Greater than or equal to)',
|
||||
validator: 'numericality',
|
||||
validatorOption: 'greaterThanOrEqualTo',
|
||||
description: 'The attribute has to be at least this value.'
|
||||
}, {
|
||||
id: 'lessThanOrEqualTo',
|
||||
name: '<= (Less than or equal to)',
|
||||
validator: 'numericality',
|
||||
validatorOption: 'lessThanOrEqualTo',
|
||||
description: 'The attribute can be this value at the most.'
|
||||
}, {
|
||||
id: 'lessThan',
|
||||
name: '< (Less than)',
|
||||
validator: 'numericality',
|
||||
validatorOption: 'lessThan',
|
||||
description: 'The attribute has to be less than this value.'
|
||||
}, {
|
||||
id: 'odd',
|
||||
name: 'Odd',
|
||||
validator: 'numericality',
|
||||
validatorOption: 'odd',
|
||||
description: 'The attribute has to be odd.'
|
||||
}, {
|
||||
id: 'even',
|
||||
name: 'Even',
|
||||
validator: 'numericality',
|
||||
validatorOption: 'even',
|
||||
description: 'The attribute has to be even.'
|
||||
}];
|
||||
|
||||
comparators.forEach(function(item) {
|
||||
comparators.forEach(item => {
|
||||
Comparators.insert(item);
|
||||
});
|
||||
|
||||
@ -71,7 +71,7 @@ const match = (metadataInstance, rules) => {
|
||||
// If no errorMessages were returned, then validation passed.
|
||||
|
||||
// Add the rule's weight to the total score
|
||||
score += rule.weight;
|
||||
score += parseInt(rule.weight, 10);
|
||||
|
||||
// Log that this rule passed in the matching details object
|
||||
details.passed.push({
|
||||
|
||||
@ -108,44 +108,6 @@ HP.ProtocolEngine = class ProtocolEngine {
|
||||
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
|
||||
@ -161,45 +123,23 @@ HP.ProtocolEngine = class ProtocolEngine {
|
||||
const studyInstance = study.getFirstInstance();
|
||||
|
||||
// Set custom attribute for study metadata
|
||||
const numberOfPriorsReferenced = this.getNumberOfAvailablePriors(study.getObjectID());
|
||||
studyInstance.setCustomAttribute('numberOfPriorsReferenced', numberOfPriorsReferenced);
|
||||
const numberOfAvailablePriors = this.getNumberOfAvailablePriors(study.getObjectID());
|
||||
|
||||
HP.ProtocolStore.getProtocol().forEach(protocol => {
|
||||
// Clone the protocol's protocolMatchingRules array
|
||||
// We clone it so that we don't accidentally add the
|
||||
// numberOfPriorsReferenced rule to the Protocol itself.
|
||||
let rules = protocol.protocolMatchingRules.slice(0);
|
||||
let rules = protocol.protocolMatchingRules.slice();
|
||||
if (!rules) {
|
||||
return;
|
||||
}
|
||||
|
||||
// To make sure the protocol has updated priors information.
|
||||
// This is reasonable for Viewers that use different APIs to
|
||||
// manage ProtocolStore or protocols.
|
||||
if (!protocol.hasUpdatedPriorsInformation) {
|
||||
|
||||
// If protocol is not an instance of HP.Protocol
|
||||
// make it be.
|
||||
if (!(protocol instanceof HP.Protocol)) {
|
||||
const protocolObject = new HP.Protocol();
|
||||
protocolObject.fromObject(protocol);
|
||||
protocol = protocolObject;
|
||||
}
|
||||
|
||||
protocol.updateNumberOfPriorsReferenced();
|
||||
}
|
||||
|
||||
// Skip protocols that require more priors than the study has
|
||||
const protocolRequiresPriors = this.protocolRequiresPriors(protocol);
|
||||
if (protocolRequiresPriors && numberOfPriorsReferenced < protocol.numberOfPriorsReferenced) {
|
||||
// Check if the study has the minimun number of priors used by the protocol.
|
||||
const numberOfPriorsReferenced = protocol.getNumberOfPriorsReferenced();
|
||||
if (numberOfPriorsReferenced > numberOfAvailablePriors) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get additional rules for number of priors referenced
|
||||
const priorsRules = this.getNumberOfPriorsRules(protocol.numberOfPriorsReferenced);
|
||||
// Concatenate rules
|
||||
rules = rules.concat(priorsRules);
|
||||
|
||||
// Run the matcher and get matching details
|
||||
const matchedDetails = HPMatcher.match(studyInstance, rules);
|
||||
const score = matchedDetails.score;
|
||||
@ -312,29 +252,6 @@ HP.ProtocolEngine = class ProtocolEngine {
|
||||
return priors instanceof Array ? priors : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array of prior studies based on current protocol matching rules.
|
||||
*
|
||||
* @param {String} studyObjectID The study object ID of the study whose priors are needed
|
||||
* @returns {Array} The array of available priors that match the given rules or an empty array
|
||||
*/
|
||||
getPriorsByProtocolMatchingRules(studyObjectID) {
|
||||
const allPriors = this.getAvailableStudyPriors(studyObjectID);
|
||||
const protocolMatchingRules = this.protocol.protocolMatchingRules;
|
||||
|
||||
if (protocolMatchingRules instanceof Array && protocolMatchingRules.length > 0) {
|
||||
return allPriors.filter(prior => {
|
||||
if (prior instanceof StudyMetadata) {
|
||||
prior = prior.getFirstInstance();
|
||||
}
|
||||
const matchDetails = HPMatcher.match(prior, protocolMatchingRules);
|
||||
return matchDetails.score > 0;
|
||||
});
|
||||
}
|
||||
|
||||
return allPriors;
|
||||
}
|
||||
|
||||
// Match images given a list of Studies and a Viewport's image matching reqs
|
||||
matchImages(viewport, viewportIndex) {
|
||||
OHIF.log.info('ProtocolEngine::matchImages');
|
||||
@ -342,7 +259,7 @@ HP.ProtocolEngine = class ProtocolEngine {
|
||||
const { studyMatchingRules, seriesMatchingRules, imageMatchingRules: instanceMatchingRules } = viewport;
|
||||
|
||||
const matchingScores = [];
|
||||
const currentStudy = this.studies[0];
|
||||
const currentStudy = this.studies[0]; // @TODO: Should this be: this.studies[this.currentStudy] ???
|
||||
const firstInstance = currentStudy.getFirstInstance();
|
||||
|
||||
let highestStudyMatchingScore = 0;
|
||||
@ -356,6 +273,9 @@ HP.ProtocolEngine = class ProtocolEngine {
|
||||
firstInstance.setCustomAttribute(ABSTRACT_PRIOR_VALUE, 0);
|
||||
}
|
||||
|
||||
// Only used if study matching rules has abstract prior values defined...
|
||||
let priorStudies;
|
||||
|
||||
studyMatchingRules.forEach(rule => {
|
||||
if (rule.attribute === ABSTRACT_PRIOR_VALUE) {
|
||||
const validatorType = Object.keys(rule.constraint)[0];
|
||||
@ -365,17 +285,20 @@ HP.ProtocolEngine = class ProtocolEngine {
|
||||
abstractPriorValue = parseInt(abstractPriorValue, 10);
|
||||
// TODO: Restrict or clarify validators for abstractPriorValue?
|
||||
|
||||
const studies = this.getPriorsByProtocolMatchingRules(currentStudy.getObjectID());
|
||||
// No need to call it more than once...
|
||||
if (!priorStudies) {
|
||||
priorStudies = this.getAvailableStudyPriors(currentStudy.getObjectID());
|
||||
}
|
||||
|
||||
// TODO: Revisit this later: What about two studies with the same
|
||||
// study date?
|
||||
|
||||
let priorStudy;
|
||||
if (abstractPriorValue === -1) {
|
||||
priorStudy = studies[studies.length - 1];
|
||||
priorStudy = priorStudies[priorStudies.length - 1];
|
||||
} else {
|
||||
const studyIndex = Math.max(abstractPriorValue - 1, 0);
|
||||
priorStudy = studies[studyIndex];
|
||||
priorStudy = priorStudies[studyIndex];
|
||||
}
|
||||
|
||||
// Invalid data
|
||||
|
||||
@ -60,7 +60,7 @@ var defaultStrategy = (function () {
|
||||
}
|
||||
|
||||
// Otherwise, return all protocols
|
||||
return HangingProtocols.find();
|
||||
return HangingProtocols.find().fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -26,6 +26,18 @@ HP.ProtocolStore = (function () {
|
||||
strategy.onReady(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a HP.Protocol instance for the given protocol object
|
||||
* @param {Object} protocolObject Protocol plain object
|
||||
* @return {HP.Protocol} HP.Protocol instance for the given protocol object
|
||||
*/
|
||||
function getProtocolInstance(protocolObject) {
|
||||
const protocolInstance = new HP.Protocol();
|
||||
protocolInstance.fromObject(protocolObject);
|
||||
|
||||
return protocolInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the hanging protocol by protocolId if defined, otherwise all stored hanging protocols
|
||||
*
|
||||
@ -35,7 +47,23 @@ HP.ProtocolStore = (function () {
|
||||
* @returns {object|array} The hanging protocol by protocolId or array of the stored hanging protocols
|
||||
*/
|
||||
function getProtocol(protocolId) {
|
||||
return strategy.getProtocol(protocolId);
|
||||
let result = strategy.getProtocol(protocolId);
|
||||
|
||||
// If result is an array of protocols objects
|
||||
if (result instanceof Array) {
|
||||
result.forEach( (protocol, index) => {
|
||||
// Check if protocol is an instance of HP.Protocol
|
||||
if (!(protocol instanceof HP.Protocol)) {
|
||||
result[index] = getProtocolInstance(protocol);
|
||||
}
|
||||
});
|
||||
} else if (result !== void 0 && !(result instanceof HP.Protocol)) {
|
||||
// Check if result exists and is not an instance of HP.Protocol
|
||||
result = getProtocolInstance(result);
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Loading…
Reference in New Issue
Block a user