Moving loadStudy logic from Hanging Protocols to StudyMetadataSource

This commit is contained in:
Eloízio Salgado committed 2017-03-02 10:18:10 -03:00
1 parent 72bfbf69c7
commit 4fe9a2f9fc
4 files changed
+190 -76

No files matched your search

@@ -8,8 +8,9 @@ import 'meteor/ohif:viewerbase';
* Import Constants * Import Constants
*/ */
const StudyMetadata = OHIF.viewerbase.metadata.StudyMetadata; const { OHIFError } = OHIF.viewerbase;
const StudySummary = OHIF.viewerbase.metadata.StudySummary; const { StudyMetadata, StudySummary } = OHIF.viewerbase.metadata;
// 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
@@ -90,40 +91,50 @@ Meteor.startup(function() {
}); });
}); });
// Log decisions regarding matching /**
HP.match = function(attributes, rules) { * Match a Metadata instance against rules using Validate.js for validation.
var options = { * @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' format: 'grouped'
}; };
var score = 0; const details = {
var details = {
passed: [], passed: [],
failed: [] failed: []
}; };
var requiredFailed = false; let requiredFailed = false;
let score = 0;
rules.forEach(rule => { 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 // not contain the attribute specified in the rule, check whether or not they have been
// defined in the CustomAttributeRetrievalCallbacks Object. // defined in the CustomAttributeRetrievalCallbacks Object.
if (!metadataInstance.customAttributeExists(attribute) &&
// TODO: Investigate why attributes.hasOwnProperty(attribute) doesn't work?
if (attributes[attribute] === undefined &&
HP.CustomAttributeRetrievalCallbacks.hasOwnProperty(attribute)) { HP.CustomAttributeRetrievalCallbacks.hasOwnProperty(attribute)) {
var customAttribute = HP.CustomAttributeRetrievalCallbacks[attribute]; const customAttribute = HP.CustomAttributeRetrievalCallbacks[attribute];
attributes[attribute] = customAttribute.callback(attributes); metadataInstance.setCustomAttribute(attribute, customAttribute.callback(metadataInstance));
} }
// Format the constraint as required by Validate.js // Format the constraint as required by Validate.js
var testConstraint = {}; const testConstraint = {
testConstraint[attribute] = rule.constraint; [attribute]: rule.constraint
};
// Use Validate.js to evaluate the constraints on the specified attributes // Create a single attribute object to be validated, since metadataInstance is an
var errorMessages = validate(attributes, testConstraint, [options]); // 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 (!errorMessages) {
// If no errorMessages were returned, then validation passed. // 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 // Log that this rule passed in the matching details object
details.passed.push({ details.passed.push({
rule: rule rule
}); });
} else { } else {
// If errorMessages were present, then validation failed // 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 // Log that this rule failed in the matching details object
// and include any error messages // and include any error messages
details.failed.push({ details.failed.push({
rule: rule, rule,
errorMessages: errorMessages errorMessages
}); });
} }
}); });
@@ -159,13 +170,13 @@ HP.match = function(attributes, rules) {
} }
return { return {
score: score, score,
details: details details
}; };
}; };
var sortByScore = function(arr) { const sortByScore = arr => {
arr.sort(function(a, b) { arr.sort((a, b) => {
return b.score - a.score; return b.score - a.score;
}); });
}; };
@@ -173,25 +184,28 @@ var sortByScore = function(arr) {
HP.ProtocolEngine = class ProtocolEngine { HP.ProtocolEngine = class ProtocolEngine {
/** /**
* Constructor * Constructor
* @param {Object} LayoutManager Layout Manager Object * @param {Object} layoutManager Layout Manager Object
* @param {Array} studies Array of study metadata * @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 * @param {Object} studyMedadataSource Instance of StudyMetadataSource (ohif-viewerbase) Object to get study metadata
*/ */
constructor(layoutManager, studies, priorStudies, studyMetadataSource) { constructor(layoutManager, studies, priorStudies, studyMetadataSource) {
const { LayoutManager, StudyMetadataSource } = OHIF.viewerbase;
// ----------- // -----------
// Validations // Type Validations
if (!(layoutManager instanceof OHIF.viewerbase.LayoutManager)) { if (!(layoutManager instanceof LayoutManager)) {
throw new OHIF.viewerbase.OHIFError('ProtocolEngine::constructor layoutManager is not an instance of LayoutManager'); throw new OHIFError('ProtocolEngine::constructor layoutManager is not an instance of LayoutManager');
} }
if (!(studyMetadataSource instanceof OHIF.viewerbase.StudyMetadataSource)) { if (!(studyMetadataSource instanceof StudyMetadataSource)) {
throw new OHIF.viewerbase.OHIFError('ProtocolEngine::constructor studyMetadataSource is not an instance of 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 // Initialization
@@ -213,7 +227,7 @@ HP.ProtocolEngine = class ProtocolEngine {
* Resets the ProtocolEngine to the best match * Resets the ProtocolEngine to the best match
*/ */
reset() { reset() {
var protocol = this.getBestMatch(); const protocol = this.getBestMatch();
this.setHangingProtocol(protocol); this.setHangingProtocol(protocol);
} }
@@ -240,7 +254,10 @@ HP.ProtocolEngine = class ProtocolEngine {
return; 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', { const rule = new HP.ProtocolMatchingRule('numberOfPriorsReferenced', {
numericality: { numericality: {
greaterThanOrEqualTo: protocol.numberOfPriorsReferenced greaterThanOrEqualTo: protocol.numberOfPriorsReferenced
@@ -356,9 +373,8 @@ HP.ProtocolEngine = class ProtocolEngine {
matchImages(viewport) { matchImages(viewport) {
OHIF.log.info('ProtocolEngine::matchImages'); OHIF.log.info('ProtocolEngine::matchImages');
const studyMatchingRules = viewport.studyMatchingRules; const { studyMatchingRules, seriesMatchingRules, imageMatchingRules: instanceMatchingRules } = viewport;
const seriesMatchingRules = viewport.seriesMatchingRules;
const instanceMatchingRules = viewport.imageMatchingRules;
const matchingScores = []; const matchingScores = [];
const currentStudy = this.studies[0]; const currentStudy = this.studies[0];
@@ -367,7 +383,8 @@ HP.ProtocolEngine = class ProtocolEngine {
let highestImageMatchingScore = 0; let highestImageMatchingScore = 0;
let bestMatch; let bestMatch;
currentStudy.abstractPriorValue = 0; // Set custom attribute for study metadata
currentStudy.setCustomAttribute('abstractPriorValue', 0);
studyMatchingRules.forEach(rule => { studyMatchingRules.forEach(rule => {
if (rule.attribute === 'abstractPriorValue') { if (rule.attribute === 'abstractPriorValue') {
@@ -391,41 +408,35 @@ HP.ProtocolEngine = class ProtocolEngine {
priorStudy = studies[studyIndex]; priorStudy = studies[studyIndex];
} }
// Invalid data
if (!(priorStudy instanceof StudyMetadata) && !(priorStudy instanceof StudySummary)) { if (!(priorStudy instanceof StudyMetadata) && !(priorStudy instanceof StudySummary)) {
return; return;
} }
// @TODO: Make sure this study is already loaded into the viewer (OHIF.viewer.Studies)
// @TypeSafeStudies
const priorStudyInstanceUID = priorStudy.getStudyInstanceUID(); const priorStudyInstanceUID = priorStudy.getStudyInstanceUID();
const alreadyLoaded = OHIF.viewer.Studies.findBy({
studyInstanceUid: priorStudyInstanceUID
});
if (!alreadyLoaded) { // Check if study metadata is already in studies list
this.studyMetadataSource.getByInstanceUID(priorStudyInstanceUID).then(study => { if (this.studies.find(study => study.getStudyInstanceUID() === priorStudyInstanceUID)) {
study.abstractPriorValue = abstractPriorValue; // Update the viewport to refresh layout manager with new study
this.updateViewports();
const studyMetadata = new OHIF.metadata.StudyMetadata(study); return;
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();
// });
} }
// 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 // TODO: Add relative Date / time
}); });
@@ -527,8 +538,8 @@ HP.ProtocolEngine = class ProtocolEngine {
OHIF.log.info('ProtocolEngine::matchImages bestMatch', bestMatch); OHIF.log.info('ProtocolEngine::matchImages bestMatch', bestMatch);
return { return {
bestMatch: bestMatch, bestMatch,
matchingScores: matchingScores matchingScores
}; };
} }
@@ -658,8 +669,7 @@ HP.ProtocolEngine = class ProtocolEngine {
} }
if (!currentViewportData.displaySetInstanceUid) { if (!currentViewportData.displaySetInstanceUid) {
//@TODO: make OHIFError use Meteor.Error instead of simple Error() throw new OHIFError('ProtocolEngine::updateViewports No matching display set found?');
throw new OHIF.viewerbase.OHIFError('ProtocolEngine::updateViewports No matching display set found?');
} }
viewportData.push(currentViewportData); viewportData.push(currentViewportData);
@@ -1,14 +1,21 @@
import { Meteor } from 'meteor/meteor'; import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating'; 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 getTimepointType = study => {
const instance = Template.instance(); const timepointApi = Template.instance().timepointApi;
const timepointApi = instance.timepointApi || instance.data.timepointApi;
if (!timepointApi) { if (!timepointApi || !(study instanceof Viewerbase.metadata.StudyMetadata)) {
return; return;
} }
const timepoint = timepointApi.study(study.studyInstanceUid)[0]; const timepoint = timepointApi.study(study.getStudyInstanceUID())[0];
if (!timepoint) { if (!timepoint) {
return; return;
} }
@@ -1,11 +1,16 @@
import { OHIF } from 'meteor/ohif:core'; import { OHIF } from 'meteor/ohif:core';
import 'meteor/ohif:viewerbase'; import 'meteor/ohif:viewerbase';
// Important metadata classes
const { OHIFError, metadata } = OHIF.viewerbase;
const { StudySummary, StudyMetadata } = metadata;
export class OHIFStudyMetadataSource extends OHIF.viewerbase.StudyMetadataSource { export class OHIFStudyMetadataSource extends OHIF.viewerbase.StudyMetadataSource {
/** /**
* Get study metadata for a study with given study InstanceUID * Get study metadata for a study with given study InstanceUID
* @param {String} studyInstanceUID Study InstanceUID * @param {String} studyInstanceUID Study InstanceUID
* @return {Promise} A Promise object
*/ */
getByInstanceUID(studyInstanceUID) { getByInstanceUID(studyInstanceUID) {
return new Promise((resolve, reject) => { 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);
}
} }
@@ -23,6 +23,15 @@ export class Metadata {
writable: false, writable: false,
value: data 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() { getData() {
@@ -38,6 +47,33 @@ export class Metadata {
return propertyValue; 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 * Static Methods
*/ */