Moving loadStudy logic from Hanging Protocols to StudyMetadataSource

This commit is contained in:
Eloízio Salgado 2017-02-10 19:13:30 -02:00
parent 72bfbf69c7
commit 4fe9a2f9fc
4 changed files with 190 additions and 76 deletions

View File

@ -8,8 +8,9 @@ import 'meteor/ohif:viewerbase';
* Import Constants
*/
const StudyMetadata = OHIF.viewerbase.metadata.StudyMetadata;
const StudySummary = OHIF.viewerbase.metadata.StudySummary;
const { OHIFError } = OHIF.viewerbase;
const { StudyMetadata, StudySummary } = OHIF.viewerbase.metadata;
// 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
@ -90,40 +91,50 @@ Meteor.startup(function() {
});
});
// Log decisions regarding matching
HP.match = function(attributes, rules) {
var options = {
/**
* Match a Metadata instance against rules using Validate.js for validation.
* @param {StudyMetadata|SeriesMetadata|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) {
const options = {
format: 'grouped'
};
var score = 0;
var details = {
const details = {
passed: [],
failed: []
};
var requiredFailed = false;
let requiredFailed = false;
let score = 0;
rules.forEach(rule => {
var attribute = rule.attribute;
const attribute = rule.attribute;
// If the attributes we are testing (e.g. study, series, or instance attributes) do
// 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.
// TODO: Investigate why attributes.hasOwnProperty(attribute) doesn't work?
if (attributes[attribute] === undefined &&
if (!metadataInstance.customAttributeExists(attribute) &&
HP.CustomAttributeRetrievalCallbacks.hasOwnProperty(attribute)) {
var customAttribute = HP.CustomAttributeRetrievalCallbacks[attribute];
attributes[attribute] = customAttribute.callback(attributes);
const customAttribute = HP.CustomAttributeRetrievalCallbacks[attribute];
metadataInstance.setCustomAttribute(attribute, customAttribute.callback(metadataInstance));
}
// Format the constraint as required by Validate.js
var testConstraint = {};
testConstraint[attribute] = rule.constraint;
const testConstraint = {
[attribute]: rule.constraint
};
// Use Validate.js to evaluate the constraints on the specified attributes
var errorMessages = validate(attributes, testConstraint, [options]);
// Create a single attribute object to be validated, since metadataInstance is an
// instance of Metadata (StudyMetadata, SeriesMetadata or InstanceMetadata)
const attributeMap = {
[attribute]: metadataInstance.getCustomAttribute(attribute)
};
// Use Validate.js to evaluate the constraints on the specified metadataInstance
const errorMessages = validate(attributeMap, testConstraint, [options]);
if (!errorMessages) {
// If no errorMessages were returned, then validation passed.
@ -133,7 +144,7 @@ HP.match = function(attributes, rules) {
// Log that this rule passed in the matching details object
details.passed.push({
rule: rule
rule
});
} else {
// If errorMessages were present, then validation failed
@ -147,8 +158,8 @@ HP.match = function(attributes, rules) {
// Log that this rule failed in the matching details object
// and include any error messages
details.failed.push({
rule: rule,
errorMessages: errorMessages
rule,
errorMessages
});
}
});
@ -159,13 +170,13 @@ HP.match = function(attributes, rules) {
}
return {
score: score,
details: details
score,
details
};
};
var sortByScore = function(arr) {
arr.sort(function(a, b) {
const sortByScore = arr => {
arr.sort((a, b) => {
return b.score - a.score;
});
};
@ -173,25 +184,28 @@ var sortByScore = function(arr) {
HP.ProtocolEngine = class ProtocolEngine {
/**
* Constructor
* @param {Object} LayoutManager Layout Manager Object
* @param {Object} layoutManager Layout Manager Object
* @param {Array} studies Array of study metadata
* @param {Array} relatedStudies Array of related studies
* @param {Map} priorStudies Map of prior studies
* @param {Object} studyMedadataSource Instance of StudyMetadataSource (ohif-viewerbase) Object to get study metadata
*/
constructor(layoutManager, studies, priorStudies, studyMetadataSource) {
const { LayoutManager, StudyMetadataSource } = OHIF.viewerbase;
// -----------
// Validations
// Type Validations
if (!(layoutManager instanceof OHIF.viewerbase.LayoutManager)) {
throw new OHIF.viewerbase.OHIFError('ProtocolEngine::constructor layoutManager is not an instance of LayoutManager');
if (!(layoutManager instanceof LayoutManager)) {
throw new OHIFError('ProtocolEngine::constructor layoutManager is not an instance of LayoutManager');
}
if (!(studyMetadataSource instanceof OHIF.viewerbase.StudyMetadataSource)) {
throw new OHIF.viewerbase.OHIFError('ProtocolEngine::constructor studyMetadataSource is not an instance of StudyMetadataSource');
if (!(studyMetadataSource instanceof StudyMetadataSource)) {
throw new OHIFError('ProtocolEngine::constructor studyMetadataSource is not an instance of StudyMetadataSource');
}
// @TODO: Validate "studies" parameter
if (!(studies instanceof Array) && !studies.every(study => study instanceof StudyMetadata)) {
throw new OHIFError('ProtocolEngine::constructor studies is not an array or it\'s items are not instances of StudyMetadata');
}
// --------------
// Initialization
@ -213,7 +227,7 @@ HP.ProtocolEngine = class ProtocolEngine {
* Resets the ProtocolEngine to the best match
*/
reset() {
var protocol = this.getBestMatch();
const protocol = this.getBestMatch();
this.setHangingProtocol(protocol);
}
@ -240,7 +254,10 @@ HP.ProtocolEngine = class ProtocolEngine {
return;
}
study.numberOfPriorsReferenced = this.getNumberOfAvailablePriors(study.getStudyInstanceUID());
// Set custom attribute for study metadata
const numberOfPriorsReferenced = this.getNumberOfAvailablePriors(study.getStudyInstanceUID());
study.setCustomAttribute('numberOfPriorsReferenced', numberOfPriorsReferenced);
const rule = new HP.ProtocolMatchingRule('numberOfPriorsReferenced', {
numericality: {
greaterThanOrEqualTo: protocol.numberOfPriorsReferenced
@ -356,9 +373,8 @@ HP.ProtocolEngine = class ProtocolEngine {
matchImages(viewport) {
OHIF.log.info('ProtocolEngine::matchImages');
const studyMatchingRules = viewport.studyMatchingRules;
const seriesMatchingRules = viewport.seriesMatchingRules;
const instanceMatchingRules = viewport.imageMatchingRules;
const { studyMatchingRules, seriesMatchingRules, imageMatchingRules: instanceMatchingRules } = viewport;
const matchingScores = [];
const currentStudy = this.studies[0];
@ -367,7 +383,8 @@ HP.ProtocolEngine = class ProtocolEngine {
let highestImageMatchingScore = 0;
let bestMatch;
currentStudy.abstractPriorValue = 0;
// Set custom attribute for study metadata
currentStudy.setCustomAttribute('abstractPriorValue', 0);
studyMatchingRules.forEach(rule => {
if (rule.attribute === 'abstractPriorValue') {
@ -391,41 +408,35 @@ HP.ProtocolEngine = class ProtocolEngine {
priorStudy = studies[studyIndex];
}
// Invalid data
if (!(priorStudy instanceof StudyMetadata) && !(priorStudy instanceof StudySummary)) {
return;
}
// @TODO: Make sure this study is already loaded into the viewer (OHIF.viewer.Studies)
// @TypeSafeStudies
const priorStudyInstanceUID = priorStudy.getStudyInstanceUID();
const alreadyLoaded = OHIF.viewer.Studies.findBy({
studyInstanceUid: priorStudyInstanceUID
});
if (!alreadyLoaded) {
this.studyMetadataSource.getByInstanceUID(priorStudyInstanceUID).then(study => {
study.abstractPriorValue = abstractPriorValue;
// Check if study metadata is already in studies list
if (this.studies.find(study => study.getStudyInstanceUID() === priorStudyInstanceUID)) {
// Update the viewport to refresh layout manager with new study
this.updateViewports();
const studyMetadata = new OHIF.metadata.StudyMetadata(study);
const displaySets = OHIF.viewerbase.sortingManager.getDisplaySets(studyMetadata);
studyMetadata.setDisplaySets(displaySets);
study.selected = true;
study.displaySets = displaySets;
OHIF.viewer.Studies.insert(study);
OHIF.viewer.StudyMetadataList.insert(studyMetadata);
}, error => { console.alert(error) });
// this.studyMetadataSource.getByInstanceUID(priorStudy.studyInstanceUid).then(, study => {
// study.abstractPriorValue = abstractPriorValue;
// study.displaySets = OHIF.viewerbase.sortingManager.getDisplaySets(study);
// OHIF.viewer.Studies.insert(study);
// this.studies.push(study);
// this.matchImages(viewport);
// this.updateViewports();
// });
return;
}
// 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 => {
// Set the custom attribute abstractPriorValue for the study metadata
studyMetadata.setCustomAttribute('abstractPriorValue', abstractPriorValue);
// Insert the new study metadata
this.studies.push(studyMetadata);
// Re-match images
this.matchImages(viewport);
}, error => {
OHIF.log.warn(error);
throw new OHIFError(`ProtocolEngine::matchImages could not get study metadata for studyInstanceUID: ${priorStudyInstanceUID}`);
});
}
// TODO: Add relative Date / time
});
@ -527,8 +538,8 @@ HP.ProtocolEngine = class ProtocolEngine {
OHIF.log.info('ProtocolEngine::matchImages bestMatch', bestMatch);
return {
bestMatch: bestMatch,
matchingScores: matchingScores
bestMatch,
matchingScores
};
}
@ -658,8 +669,7 @@ HP.ProtocolEngine = class ProtocolEngine {
}
if (!currentViewportData.displaySetInstanceUid) {
//@TODO: make OHIFError use Meteor.Error instead of simple Error()
throw new OHIF.viewerbase.OHIFError('ProtocolEngine::updateViewports No matching display set found?');
throw new OHIFError('ProtocolEngine::updateViewports No matching display set found?');
}
viewportData.push(currentViewportData);

View File

@ -1,14 +1,21 @@
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import { Viewerbase } from 'meteor/ohif:viewerbase';
// TODO: [LT-refactor] move this to ohif:hanging-protocols package
/**
* Get a timepoint type for a given study metadata
* @param {StudyMetadata} study StudyMetadata instance
* @return {String|undefined} Timepoint type if found or undefined if not found or any error/missing information
*/
const getTimepointType = study => {
const instance = Template.instance();
const timepointApi = instance.timepointApi || instance.data.timepointApi;
if (!timepointApi) {
const timepointApi = Template.instance().timepointApi;
if (!timepointApi || !(study instanceof Viewerbase.metadata.StudyMetadata)) {
return;
}
const timepoint = timepointApi.study(study.studyInstanceUid)[0];
const timepoint = timepointApi.study(study.getStudyInstanceUID())[0];
if (!timepoint) {
return;
}

View File

@ -1,11 +1,16 @@
import { OHIF } from 'meteor/ohif:core';
import 'meteor/ohif:viewerbase';
// Important metadata classes
const { OHIFError, metadata } = OHIF.viewerbase;
const { StudySummary, StudyMetadata } = metadata;
export class OHIFStudyMetadataSource extends OHIF.viewerbase.StudyMetadataSource {
/**
* Get study metadata for a study with given study InstanceUID
* @param {String} studyInstanceUID Study InstanceUID
* @return {Promise} A Promise object
*/
getByInstanceUID(studyInstanceUID) {
return new Promise((resolve, reject) => {
@ -13,4 +18,60 @@ export class OHIFStudyMetadataSource extends OHIF.viewerbase.StudyMetadataSource
});
}
/**
* Load study info (OHIF.viewer.Studies) and study metadata (OHIF.viewer.StudyMetadataList) for a given study.
* @param {StudySummary|StudyMetadata} Instance of StudySummary or StudyMetadata object
*/
loadStudy(study) {
if (!(study instanceof StudyMetadata) && !(study instanceof StudySummary)) {
throw new OHIFError('OHIFStudyMetadataSource::loadStudy study is not an instance of StudySummary or StudyMetadata');
}
return new Promise((resolve, reject) => {
const studyInstanceUID = study.getStudyInstanceUID();
if (study instanceof StudyMetadata) {
const alreadyLoaded = OHIF.viewer.Studies.findBy({
studyInstanceUid: studyInstanceUID
});
if (!alreadyLoaded) {
this._updateStudyCollections(study);
}
resolve(study);
return;
}
this.getByInstanceUID(study.getStudyInstanceUID()).then(studyInfo => {
// Create study metadata object
const studyMetadata = new OHIF.metadata.StudyMetadata(studyInfo);
// Get Study display sets
const displaySets = OHIF.viewerbase.sortingManager.getDisplaySets(studyMetadata);
// Set studyMetadata display sets
studyMetadata.setDisplaySets(displaySets);
this._updateStudyCollections(studyMetadata);
resolve(studyMetadata);
}, reject);
});
}
// Static methods
static _updateStudyCollections(studyMetadata) {
const studyInfo = studyMetadata.getData();
// Set some studyInfo properties
studyInfo.selected = true;
studyInfo.displaySets = displaySets;
// Insert new study info object in Studies TypeSafeCollection
OHIF.viewer.Studies.insert(studyInfo);
// Insert new study metadata in StudyMetadataList TypeSafeCollection
OHIF.viewer.StudyMetadataList.insert(studyMetadata);
}
}

View File

@ -23,6 +23,15 @@ export class Metadata {
writable: false,
value: data
});
// Define _custom properties as an immutable property
// IMPORTANT: This property can only be set during instance construction.
Object.defineProperty(this, '_custom', {
configurable: false,
enumerable: false,
writable: false,
value: Object.create(null)
});
}
getData() {
@ -38,6 +47,33 @@ export class Metadata {
return propertyValue;
}
/**
* Set custom attribute value
* @param {String} attribute Custom attribute name
* @param {Any} value Custom attribute value
*/
setCustomAttribute(attribute, value) {
this._custom[attribute] = value;
}
/**
* Get custom attribute value
* @param {String} attribute Custom attribute name
* @return {Any} Custom attribute value
*/
getCustomAttribute(attribute) {
return this._custom[attribute];
}
/**
* Check if a custom attribute exists
* @param {String} attribute Custom attribute name
* @return {Boolean} True if custom attribute exists or false if not
*/
customAttributeExists(attribute) {
return attribute in this._custom;
}
/**
* Static Methods
*/