Moving loadStudy logic from Hanging Protocols to StudyMetadataSource
This commit is contained in:
parent
72bfbf69c7
commit
4fe9a2f9fc
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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
|
||||
*/
|
||||
|
||||
Loading…
Reference in New Issue
Block a user