Updating OHIF private hanging-protocol branch with Nucleus rebased version

This commit is contained in:
Emanuel F. Oliveira 2017-02-10 08:02:11 -02:00 committed by Eloízio Salgado
parent 5346f20329
commit b9a3cf9da3
3 changed files with 152 additions and 60 deletions

View File

@ -1,5 +1,4 @@
import { Meteor } from 'meteor/meteor'; import { Meteor } from 'meteor/meteor';
import { $ } from 'meteor/jquery';
import { _ } from 'meteor/underscore'; import { _ } from 'meteor/underscore';
// OHIF Modules // OHIF Modules
import { OHIF } from 'meteor/ohif:core'; import { OHIF } from 'meteor/ohif:core';
@ -173,6 +172,8 @@ HP.ProtocolEngine = class ProtocolEngine {
* @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, relatedStudies, studyMetadataSource) { constructor(LayoutManager, studies, relatedStudies, studyMetadataSource) {
OHIF.log.info('ProtocolEngine::constructor');
if (!(studyMetadataSource instanceof OHIF.viewerbase.StudyMetadataSource)) { if (!(studyMetadataSource instanceof OHIF.viewerbase.StudyMetadataSource)) {
throw new OHIF.viewerbase.OHIFError('ProtocolEngine::constructor studyMetadataSource is not an instance of StudyMetadataSource'); throw new OHIF.viewerbase.OHIFError('ProtocolEngine::constructor studyMetadataSource is not an instance of StudyMetadataSource');
} }
@ -207,19 +208,21 @@ HP.ProtocolEngine = class ProtocolEngine {
} }
findMatchByStudy(study) { findMatchByStudy(study) {
var matched = []; OHIF.log.info('ProtocolEngine::findMatchByStudy');
const matched = [];
HP.ProtocolStore.getProtocol().forEach(protocol => { HP.ProtocolStore.getProtocol().forEach(protocol => {
// Clone the protocol's protocolMatchingRules array // Clone the protocol's protocolMatchingRules array
// We clone it so that we don't accidentally add the // We clone it so that we don't accidentally add the
// numberOfPriorsReferenced rule to the Protocol itself. // numberOfPriorsReferenced rule to the Protocol itself.
var rules = protocol.protocolMatchingRules.slice(0); const rules = protocol.protocolMatchingRules.slice(0);
if (!rules) { if (!rules) {
return; return;
} }
study.numberOfPriorsReferenced = this.getNumberOfAvailablePriors(study); study.numberOfPriorsReferenced = this.getNumberOfAvailablePriors(study);
var rule = new HP.ProtocolMatchingRule('numberOfPriorsReferenced', { const rule = new HP.ProtocolMatchingRule('numberOfPriorsReferenced', {
numericality: { numericality: {
greaterThanOrEqualTo: protocol.numberOfPriorsReferenced greaterThanOrEqualTo: protocol.numberOfPriorsReferenced
} }
@ -227,7 +230,7 @@ HP.ProtocolEngine = class ProtocolEngine {
rules.push(rule); rules.push(rule);
var matchedDetails = HP.match(study, rules); const matchedDetails = HP.match(study, rules);
if (matchedDetails.score > 0) { if (matchedDetails.score > 0) {
matched.push({ matched.push({
@ -238,7 +241,7 @@ HP.ProtocolEngine = class ProtocolEngine {
}); });
if (!matched.length) { if (!matched.length) {
var defaultProtocol = HP.ProtocolStore.getProtocol('defaultProtocol'); const defaultProtocol = HP.ProtocolStore.getProtocol('defaultProtocol');
return [{ return [{
score: 1, score: 1,
@ -247,6 +250,9 @@ HP.ProtocolEngine = class ProtocolEngine {
} }
sortByScore(matched); sortByScore(matched);
OHIF.log.info('ProtocolEngine::findMatchByStudy matched', matched);
return matched; return matched;
} }
@ -254,21 +260,23 @@ HP.ProtocolEngine = class ProtocolEngine {
* Populates the MatchedProtocols Collection by running the matching procedure * Populates the MatchedProtocols Collection by running the matching procedure
*/ */
updateMatches() { updateMatches() {
OHIF.log.info('ProtocolEngine::updateMatches');
// Clear all data from the MatchedProtocols Collection // Clear all data from the MatchedProtocols Collection
MatchedProtocols.remove({}); MatchedProtocols.remove({});
// For each study, find the matching protocols // For each study, find the matching protocols
this.studies.forEach(study => { this.studies.forEach(study => {
var matched = this.findMatchByStudy(study); const matched = this.findMatchByStudy(study);
// For each matched protocol, check if it is already in MatchedProtocols // For each matched protocol, check if it is already in MatchedProtocols
matched.forEach(function(matchedDetail) { matched.forEach(matchedDetail => {
var protocol = matchedDetail.protocol; const protocol = matchedDetail.protocol;
if (!protocol) { if (!protocol) {
return; return;
} }
var protocolInCollection = MatchedProtocols.findOne({ const protocolInCollection = MatchedProtocols.findOne({
id: protocol.id id: protocol.id
}); });
@ -289,15 +297,18 @@ HP.ProtocolEngine = class ProtocolEngine {
this.updateMatches(); this.updateMatches();
// Retrieve the highest scoring Protocol // Retrieve the highest scoring Protocol
var sorted = MatchedProtocols.find({}, { const sorted = MatchedProtocols.find({}, {
sort: { sort: {
score: -1 score: -1
}, },
limit: 1 limit: 1
}).fetch(); }).fetch();
// Return the highest scoring Protocol // Highest scoring Protocol
return sorted[0].protocol; const bestMatch = sorted[0].protocol;
OHIF.log.info('ProtocolEngine::getBestMatch bestMatch', bestMatch);
return bestMatch;
} }
/** /**
@ -366,36 +377,34 @@ HP.ProtocolEngine = class ProtocolEngine {
}); });
} }
getRelatedStudies(options, sorting) {
HP.setGetRelatedStudies = getNucleusRelatedStudies();
HP.getRelatedStudies(options, sorting);
}
// Match images given a list of Studies and a Viewport's image matching reqs // Match images given a list of Studies and a Viewport's image matching reqs
matchImages(viewport) { matchImages(viewport) {
var studyMatchingRules = viewport.studyMatchingRules; OHIF.log.info('ProtocolEngine::matchImages');
var seriesMatchingRules = viewport.seriesMatchingRules;
var instanceMatchingRules = viewport.imageMatchingRules;
var highestStudyMatchingScore = 0; const studyMatchingRules = viewport.studyMatchingRules;
var highestSeriesMatchingScore = 0; const seriesMatchingRules = viewport.seriesMatchingRules;
var highestImageMatchingScore = 0; const instanceMatchingRules = viewport.imageMatchingRules;
var matchingScores = []; const matchingScores = [];
var bestMatch; const currentStudy = this.studies[0];
let highestStudyMatchingScore = 0;
let highestSeriesMatchingScore = 0;
let highestImageMatchingScore = 0;
let bestMatch;
var currentStudy = this.studies[0];
currentStudy.abstractPriorValue = 0; currentStudy.abstractPriorValue = 0;
studyMatchingRules.forEach(rule => { studyMatchingRules.forEach(rule => {
if (rule.attribute === 'abstractPriorValue') { if (rule.attribute === 'abstractPriorValue') {
var validatorType = Object.keys(rule.constraint)[0]; const validatorType = Object.keys(rule.constraint)[0];
var validator = Object.keys(rule.constraint[validatorType])[0]; const validator = Object.keys(rule.constraint[validatorType])[0];
var abstractPriorValue = rule.constraint[validatorType][validator];
let abstractPriorValue = rule.constraint[validatorType][validator];
abstractPriorValue = parseInt(abstractPriorValue, 10); abstractPriorValue = parseInt(abstractPriorValue, 10);
// TODO: Restrict or clarify validators for abstractPriorValue? // TODO: Restrict or clarify validators for abstractPriorValue?
// @TODO replace for this.relatedStudies (TypeSafeCollection of StudyMetadatas) // @TODO replace for this.relatedStudies (TypeSafeCollection of StudyMetadatas)
var studies = StudyListStudies.find({ const studies = StudyListStudies.find({
patientId: currentStudy.patientId, patientId: currentStudy.patientId,
studyDate: { studyDate: {
$lt: currentStudy.studyDate $lt: currentStudy.studyDate
@ -409,11 +418,11 @@ HP.ProtocolEngine = class ProtocolEngine {
// TODO: Revisit this later: What about two studies with the same // TODO: Revisit this later: What about two studies with the same
// study date? // study date?
var priorStudy; let priorStudy;
if (abstractPriorValue === -1) { if (abstractPriorValue === -1) {
priorStudy = studies[studies.length - 1]; priorStudy = studies[studies.length - 1];
} else { } else {
var studyIndex = Math.max(abstractPriorValue - 1, 0); const studyIndex = Math.max(abstractPriorValue - 1, 0);
priorStudy = studies[studyIndex]; priorStudy = studies[studyIndex];
} }
@ -422,7 +431,7 @@ HP.ProtocolEngine = class ProtocolEngine {
} }
// @TypeSafeStudies // @TypeSafeStudies
var alreadyLoaded = OHIF.viewer.Studies.findBy({ const alreadyLoaded = OHIF.viewer.Studies.findBy({
studyInstanceUid: priorStudy.studyInstanceUid studyInstanceUid: priorStudy.studyInstanceUid
}); });
@ -441,7 +450,7 @@ HP.ProtocolEngine = class ProtocolEngine {
// TODO: Add relative Date / time // TODO: Add relative Date / time
}); });
this.studies.forEach(function(study) { this.studies.forEach(study => {
const studyMatchDetails = HP.match(study, studyMatchingRules); const studyMatchDetails = HP.match(study, studyMatchingRules);
if ((studyMatchingRules.length && !studyMatchDetails.score) || if ((studyMatchingRules.length && !studyMatchDetails.score) ||
studyMatchDetails.score < highestStudyMatchingScore) { studyMatchDetails.score < highestStudyMatchingScore) {
@ -450,7 +459,7 @@ HP.ProtocolEngine = class ProtocolEngine {
highestStudyMatchingScore = studyMatchDetails.score; highestStudyMatchingScore = studyMatchDetails.score;
study.forEachSeries(function(series) { study.forEachSeries(series => {
const seriesMatchDetails = HP.match(series, seriesMatchingRules); const seriesMatchDetails = HP.match(series, seriesMatchingRules);
if ((seriesMatchingRules.length && !seriesMatchDetails.score) || if ((seriesMatchingRules.length && !seriesMatchDetails.score) ||
seriesMatchDetails.score < highestSeriesMatchingScore) { seriesMatchDetails.score < highestSeriesMatchingScore) {
@ -459,7 +468,7 @@ HP.ProtocolEngine = class ProtocolEngine {
highestSeriesMatchingScore = seriesMatchDetails.score; highestSeriesMatchingScore = seriesMatchDetails.score;
series.forEachInstance(function(instance, index) { series.forEachInstance((instance, index) => {
// This tests to make sure there is actually image data in this instance // This tests to make sure there is actually image data in this instance
// TODO: Change this when we add PDF and MPEG support // TODO: Change this when we add PDF and MPEG support
// See https://ohiforg.atlassian.net/browse/LT-227 // See https://ohiforg.atlassian.net/browse/LT-227
@ -485,11 +494,12 @@ HP.ProtocolEngine = class ProtocolEngine {
matchDetails.failed = matchDetails.failed.concat(studyMatchDetails.details.failed); matchDetails.failed = matchDetails.failed.concat(studyMatchDetails.details.failed);
const totalMatchScore = instanceMatchDetails.score + seriesMatchDetails.score + studyMatchDetails.score; const totalMatchScore = instanceMatchDetails.score + seriesMatchDetails.score + studyMatchDetails.score;
const currentSOPInstanceUID = instance.getSOPInstanceUID();
const imageDetails = { const imageDetails = {
studyInstanceUid: study.getStudyInstanceUID(), studyInstanceUid: study.getStudyInstanceUID(),
seriesInstanceUid: series.getSeriesInstanceUID(), seriesInstanceUid: series.getSeriesInstanceUID(),
sopInstanceUid: instance.getSOPInstanceUID(), sopInstanceUid: currentSOPInstanceUID,
currentImageIdIndex: index, currentImageIdIndex: index,
matchingScore: totalMatchScore, matchingScore: totalMatchScore,
matchDetails: matchDetails, matchDetails: matchDetails,
@ -502,7 +512,6 @@ HP.ProtocolEngine = class ProtocolEngine {
}; };
// Find the displaySet // Find the displaySet
const currentSOPInstanceUID = instance.getSOPInstanceUID();
const displaySet = study.findDisplaySet(displaySet => displaySet.images.find(image => image.getSOPInstanceUID() === currentSOPInstanceUID)); const displaySet = study.findDisplaySet(displaySet => displaySet.images.find(image => image.getSOPInstanceUID() === currentSOPInstanceUID));
// If the instance was found, set the displaySet ID // If the instance was found, set the displaySet ID
@ -535,6 +544,8 @@ HP.ProtocolEngine = class ProtocolEngine {
}); });
matchingScores.sort((a, b) => sortingFunction(a.sortingInfo, b.sortingInfo)); matchingScores.sort((a, b) => sortingFunction(a.sortingInfo, b.sortingInfo));
OHIF.log.info('ProtocolEngine::matchImages bestMatch', bestMatch);
return { return {
bestMatch: bestMatch, bestMatch: bestMatch,
matchingScores: matchingScores matchingScores: matchingScores
@ -551,13 +562,15 @@ HP.ProtocolEngine = class ProtocolEngine {
* @param viewportIndex * @param viewportIndex
*/ */
updateViewports(viewportIndex) { updateViewports(viewportIndex) {
OHIF.log.info(`ProtocolEngine::updateViewports viewportIndex: ${viewportIndex}`);
// Make sure we have an active protocol with a non-empty array of display sets // Make sure we have an active protocol with a non-empty array of display sets
if (!this.protocol || !this.protocol.stages || !this.protocol.stages.length) { if (!this.protocol || !this.protocol.stages || !this.protocol.stages.length) {
return; return;
} }
// Retrieve the current display set in the display set sequence // Retrieve the current display set in the display set sequence
var stageModel = this.getCurrentStageModel(); const stageModel = this.getCurrentStageModel();
// If the current display set does not fulfill the requirements to be displayed, // If the current display set does not fulfill the requirements to be displayed,
// stop here. // stop here.
@ -570,20 +583,20 @@ HP.ProtocolEngine = class ProtocolEngine {
// Retrieve the layoutTemplate associated with the current display set's viewport structure // Retrieve the layoutTemplate associated with the current display set's viewport structure
// If no such template name exists, stop here. // If no such template name exists, stop here.
var layoutTemplateName = stageModel.viewportStructure.getLayoutTemplateName(); const layoutTemplateName = stageModel.viewportStructure.getLayoutTemplateName();
if (!layoutTemplateName) { if (!layoutTemplateName) {
return; return;
} }
// Retrieve the properties associated with the current display set's viewport structure template // Retrieve the properties associated with the current display set's viewport structure template
// If no such layout properties exist, stop here. // If no such layout properties exist, stop here.
var layoutProps = stageModel.viewportStructure.properties; const layoutProps = stageModel.viewportStructure.properties;
if (!layoutProps) { if (!layoutProps) {
return; return;
} }
// Create an empty array to store the output viewportData // Create an empty array to store the output viewportData
var viewportData = []; const viewportData = [];
// Empty the matchDetails associated with the ProtocolEngine. // Empty the matchDetails associated with the ProtocolEngine.
// This will be used to store the pass/fail details and score // This will be used to store the pass/fail details and score
@ -592,14 +605,18 @@ HP.ProtocolEngine = class ProtocolEngine {
// Loop through each viewport // Loop through each viewport
stageModel.viewports.forEach((viewport, viewportIndex) => { stageModel.viewports.forEach((viewport, viewportIndex) => {
var details = this.matchImages(viewport); const details = this.matchImages(viewport);
this.matchDetails[viewportIndex] = details; this.matchDetails[viewportIndex] = details;
// Convert any YES/NO values into true/false for Cornerstone // Convert any YES/NO values into true/false for Cornerstone
var cornerstoneViewportParams = {}; const cornerstoneViewportParams = {};
Object.keys(viewport.viewportSettings).forEach(function(key) {
var value = viewport.viewportSettings[key]; // Cache viewportSettings keys
const viewportSettingsKeys = Object.keys(viewport.viewportSettings);
viewportSettingsKeys.forEach(key => {
let value = viewport.viewportSettings[key];
if (value === 'YES') { if (value === 'YES') {
value = true; value = true;
} else if (value === 'NO') { } else if (value === 'NO') {
@ -611,14 +628,15 @@ HP.ProtocolEngine = class ProtocolEngine {
// imageViewerViewports occasionally needs relevant layout data in order to set // imageViewerViewports occasionally needs relevant layout data in order to set
// the element style of the viewport in question // the element style of the viewport in question
var currentViewportData = $.extend({ const currentViewportData = {
viewportIndex: viewportIndex, viewportIndex,
viewport: cornerstoneViewportParams viewport: cornerstoneViewportParams,
}, layoutProps); ...layoutProps
};
var customSettings = []; const customSettings = [];
Object.keys(viewport.viewportSettings).forEach(id => { viewportSettingsKeys.forEach(id => {
var setting = HP.CustomViewportSettings[id]; const setting = HP.CustomViewportSettings[id];
if (!setting) { if (!setting) {
return; return;
} }
@ -629,13 +647,13 @@ HP.ProtocolEngine = class ProtocolEngine {
}); });
}); });
currentViewportData.renderedCallback = function(element) { currentViewportData.renderedCallback = element => {
//console.log('renderedCallback for ' + element.id); //console.log('renderedCallback for ' + element.id);
customSettings.forEach(function(customSetting) { customSettings.forEach(customSetting => {
console.log('Applying custom setting: ' + customSetting.id); OHIF.log.info(`ProtocolEngine::currentViewportData.renderedCallback Applying custom setting: ${customSetting.id}`);
console.log('with value: ' + customSetting.value); OHIF.log.info(`ProtocolEngine::currentViewportData.renderedCallback with value: ${customSetting.value}`);
var setting = HP.CustomViewportSettings[customSetting.id]; const setting = HP.CustomViewportSettings[customSetting.id];
setting.callback(element, customSetting.value); setting.callback(element, customSetting.value);
}); });
}; };
@ -686,6 +704,9 @@ HP.ProtocolEngine = class ProtocolEngine {
* @param updateViewports * @param updateViewports
*/ */
setHangingProtocol(newProtocol, updateViewports=true) { setHangingProtocol(newProtocol, updateViewports=true) {
OHIF.log.info('ProtocolEngine::setHangingProtocol newProtocol', newProtocol);
OHIF.log.info(`ProtocolEngine::setHangingProtocol updateViewports = ${updateViewports}`);
// Reset the array of newStageIds // Reset the array of newStageIds
this.newStageIds = []; this.newStageIds = [];
@ -726,6 +747,8 @@ HP.ProtocolEngine = class ProtocolEngine {
* @param stage An integer value specifying the index of the desired Stage * @param stage An integer value specifying the index of the desired Stage
*/ */
setCurrentProtocolStage(stage) { setCurrentProtocolStage(stage) {
OHIF.log.info(`ProtocolEngine::setCurrentProtocolStage stage = ${stage}`);
if (!this.protocol || !this.protocol.stages || !this.protocol.stages.length) { if (!this.protocol || !this.protocol.stages || !this.protocol.stages.length) {
return; return;
} }

View File

@ -14,7 +14,7 @@ export class ImageSet {
constructor(images) { constructor(images) {
if (Array.isArray(images) !== true) { if (Array.isArray(images) !== true) {
throw new OHIFError('ImageSet expects an array of images...'); throw new OHIFError('ImageSet expects an array of images');
} }
// @property "images" // @property "images"

View File

@ -1,6 +1,7 @@
import { Metadata } from './Metadata'; import { Metadata } from './Metadata';
import { SeriesMetadata } from './SeriesMetadata'; import { SeriesMetadata } from './SeriesMetadata';
import { ImageSet } from '../ImageSet'; import { ImageSet } from '../ImageSet';
import { OHIFError } from '../OHIFError';
export class StudyMetadata extends Metadata { export class StudyMetadata extends Metadata {
@ -122,6 +123,14 @@ export class StudyMetadata extends Metadata {
} }
} }
/**
* Retrieve the number of display sets within the current study.
* @returns {number} The number of display sets in the current study.
*/
getDisplaySetCount() {
return this._displaySets.length;
}
/** /**
* Returns the StudyInstanceUID of the current study. * Returns the StudyInstanceUID of the current study.
*/ */
@ -129,6 +138,14 @@ export class StudyMetadata extends Metadata {
return this._studyInstanceUID; return this._studyInstanceUID;
} }
/**
* Getter for series
* @return {Array} Array of SeriesMetadata object
*/
getSeries() {
return this._series.slice();
}
/** /**
* Append a series to the current study. * Append a series to the current study.
* @param {SeriesMetadata} series The series to be added to the current study. * @param {SeriesMetadata} series The series to be added to the current study.
@ -213,6 +230,58 @@ export class StudyMetadata extends Metadata {
return this._series.indexOf(series); return this._series.indexOf(series);
} }
/**
* It sorts the series based on display sets order. Each series must be an instance
* of SeriesMetadata and each display sets must be an instance of ImageSet.
* Both array must have the same length, otherwise it throws an error.
* Useful example of usage:
* Study data provided by backend does not sort series at all and client-side
* needs series sorted by the same criteria used for sorting display sets.
*/
sortSeriesByDisplaySets() {
// Check if the study is multiframe. If it is,
const firstInstance = this.getFirstInstance();
const isMultiframe = firstInstance.getRawValue('x00280008') > 1;
// @TODO: Check if is necessary to do a similar check for multiframe studies
// Check if both arrays have same length in case of non-multiframe studies
if (!isMultiframe && this.getSeriesCount() !== this.getDisplaySetCount()) {
throw new OHIFError('StudyMetadata::sortSeriesByDisplaySets series count and display set count does not match');
}
// Object for mapping display sets' index by seriesInstanceUid
const displaySetsMapping = {};
// Loop through each display set to create the mapping
this.forEachDisplaySet( (displaySet, index) => {
if (!(displaySet instanceof ImageSet)) {
throw new OHIFError(`StudyMetadata::sortSeriesByDisplaySets display set at index ${index} is not an instance of ImageSet`);
}
// In case of multiframe studies, just get the first index occurence
if (displaySetsMapping[displaySet.seriesInstanceUid] === void 0) {
displaySetsMapping[displaySet.seriesInstanceUid] = index;
}
});
// Clone of actual series
const actualSeries = this.getSeries();
actualSeries.forEach( (series, index) => {
if (!(series instanceof SeriesMetadata)) {
throw new OHIFError(`StudyMetadata::sortSeriesByDisplaySets series at index ${index} is not an instance of SeriesMetadata`);
}
// Get the new series index
const seriesIndex = displaySetsMapping[series.getSeriesInstanceUID()];
// Update the series object with the new series position
this._series[seriesIndex] = series;
});
}
/** /**
* Compares the current study instance with another one. * Compares the current study instance with another one.
* @param {StudyMetadata} study An instance of the StudyMetadata class. * @param {StudyMetadata} study An instance of the StudyMetadata class.