Moving Hanging Protocols updates back to the viewers
This commit is contained in:
parent
e10453b919
commit
bec8f21c0a
@ -89,7 +89,7 @@ Template.viewer.onCreated(() => {
|
|||||||
OHIF.viewer.StudyMetadataList.removeAll();
|
OHIF.viewer.StudyMetadataList.removeAll();
|
||||||
|
|
||||||
instance.data.studies.forEach(study => {
|
instance.data.studies.forEach(study => {
|
||||||
const studyMetadata = new OHIF.metadata.StudyMetadata(study);
|
const studyMetadata = new OHIF.metadata.StudyMetadata(study, study.studyInstanceUid);
|
||||||
const displaySets = OHIF.viewerbase.sortingManager.getDisplaySets(studyMetadata);
|
const displaySets = OHIF.viewerbase.sortingManager.getDisplaySets(studyMetadata);
|
||||||
|
|
||||||
studyMetadata.setDisplaySets(displaySets);
|
studyMetadata.setDisplaySets(displaySets);
|
||||||
@ -125,6 +125,7 @@ Template.viewer.onCreated(() => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @TODO: Maybe this should be a setCustomAttribute?
|
||||||
study.timepointType = timepoint.timepointType;
|
study.timepointType = timepoint.timepointType;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -112,7 +112,7 @@ Template.viewer.onCreated(() => {
|
|||||||
|
|
||||||
ViewerData[contentId].studyInstanceUids = [];
|
ViewerData[contentId].studyInstanceUids = [];
|
||||||
instance.data.studies.forEach(study => {
|
instance.data.studies.forEach(study => {
|
||||||
const studyMetadata = new OHIF.metadata.StudyMetadata(study);
|
const studyMetadata = new OHIF.metadata.StudyMetadata(study, study.studyInstanceUid);
|
||||||
const displaySets = OHIF.viewerbase.sortingManager.getDisplaySets(studyMetadata);
|
const displaySets = OHIF.viewerbase.sortingManager.getDisplaySets(studyMetadata);
|
||||||
|
|
||||||
studyMetadata.setDisplaySets(displaySets);
|
studyMetadata.setDisplaySets(displaySets);
|
||||||
|
|||||||
@ -12,3 +12,10 @@ HangingProtocols.allow({
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// @TODO: Remove this after stabilizing ProtocolEngine
|
||||||
|
if (Meteor.isDevelopment && Meteor.isServer) {
|
||||||
|
Meteor.startup(() => {
|
||||||
|
HangingProtocols.remove({});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,6 @@
|
|||||||
import { Meteor } from 'meteor/meteor';
|
import { Meteor } from 'meteor/meteor';
|
||||||
import { _ } from 'meteor/underscore';
|
import { _ } from 'meteor/underscore';
|
||||||
|
|
||||||
// OHIF Modules
|
// OHIF Modules
|
||||||
import { OHIF } from 'meteor/ohif:core';
|
import { OHIF } from 'meteor/ohif:core';
|
||||||
import 'meteor/ohif:viewerbase';
|
import 'meteor/ohif:viewerbase';
|
||||||
@ -84,8 +85,8 @@ HP.addCustomViewportSetting = function(settingId, settingName, options, callback
|
|||||||
};
|
};
|
||||||
|
|
||||||
Meteor.startup(function() {
|
Meteor.startup(function() {
|
||||||
HP.addCustomViewportSetting('wlPreset', 'Window/Level Preset', Object.keys(OHIF.viewer.wlPresets), function(element, optionValue) {
|
HP.addCustomViewportSetting('wlPreset', 'Window/Level Preset', Object.create(null), function(element, optionValue) {
|
||||||
if (OHIF.viewer.wlPresets.hasOwnProperty(optionValue)) {
|
if (optionValue in OHIF.viewer.wlPresets) {
|
||||||
OHIF.viewerbase.wlPresets.applyWLPreset(optionValue, element);
|
OHIF.viewerbase.wlPresets.applyWLPreset(optionValue, element);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -93,14 +94,15 @@ Meteor.startup(function() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Match a Metadata instance against rules using Validate.js for validation.
|
* Match a Metadata instance against rules using Validate.js for validation.
|
||||||
* @param {StudyMetadata|SeriesMetadata|InstanceMetadata} metadataInstance Metadata instance object
|
* @param {StudySummary|InstanceMetadata} metadataInstance Metadata instance object
|
||||||
* @param {Array} rules Array of MatchingRules instances (StudyMatchingRule|SeriesMatchingRule|ImageMatchingRule) for the match
|
* @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)
|
* @return {Object} Matching Object with score and details (which rule passed or failed)
|
||||||
*/
|
*/
|
||||||
HP.match = function(metadataInstance, rules) {
|
HP.match = function(metadataInstance, rules) {
|
||||||
|
|
||||||
if (!(metadataInstance instanceof StudyMetadata || metadataInstance instanceof SeriesMetadata || metadataInstance instanceof InstanceMetadata)) {
|
// Make sure the supplied data is valid.
|
||||||
throw new OHIFError('HP::match metadataInstance must be an instance of StudyMetadata, SeriesMetadata or InstanceMetadata');
|
if (!(metadataInstance instanceof StudySummary || metadataInstance instanceof InstanceMetadata)) {
|
||||||
|
throw new OHIFError('HP::match metadataInstance must be an instance of StudySummary or InstanceMetadata');
|
||||||
}
|
}
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
@ -111,18 +113,9 @@ HP.match = function(metadataInstance, rules) {
|
|||||||
passed: [],
|
passed: [],
|
||||||
failed: []
|
failed: []
|
||||||
};
|
};
|
||||||
|
|
||||||
let requiredFailed = false;
|
let requiredFailed = false;
|
||||||
let score = 0;
|
let score = 0;
|
||||||
let instance;
|
|
||||||
|
|
||||||
if (metadataInstance instanceof StudyMetadata) {
|
|
||||||
instance = metadataInstance.getFirstInstance();
|
|
||||||
} else if (metadataInstance instanceof SeriesMetadata) {
|
|
||||||
instance = metadataInstance.getInstanceByIndex(0);
|
|
||||||
} else {
|
|
||||||
instance = metadataInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
rules.forEach(rule => {
|
rules.forEach(rule => {
|
||||||
const attribute = rule.attribute;
|
const attribute = rule.attribute;
|
||||||
@ -144,13 +137,18 @@ HP.match = function(metadataInstance, rules) {
|
|||||||
|
|
||||||
// Create a single attribute object to be validated, since metadataInstance is an
|
// Create a single attribute object to be validated, since metadataInstance is an
|
||||||
// instance of Metadata (StudyMetadata, SeriesMetadata or InstanceMetadata)
|
// instance of Metadata (StudyMetadata, SeriesMetadata or InstanceMetadata)
|
||||||
const attributeValue = customAttributeExists ? metadataInstance.getCustomAttribute(attribute) : instance.getRawValue(attribute);
|
const attributeValue = customAttributeExists ? metadataInstance.getCustomAttribute(attribute) : metadataInstance.getTagValue(attribute);
|
||||||
const attributeMap = {
|
const attributeMap = {
|
||||||
[attribute]: attributeValue + ''
|
[attribute]: attributeValue
|
||||||
};
|
};
|
||||||
|
|
||||||
// Use Validate.js to evaluate the constraints on the specified metadataInstance
|
// Use Validate.js to evaluate the constraints on the specified metadataInstance
|
||||||
const errorMessages = validate(attributeMap, testConstraint, [options]);
|
let errorMessages;
|
||||||
|
try {
|
||||||
|
errorMessages = validate(attributeMap, testConstraint, [options]);
|
||||||
|
} catch (e) {
|
||||||
|
errorMessages = [ 'Something went wrong during validation.', e ];
|
||||||
|
}
|
||||||
|
|
||||||
if (!errorMessages) {
|
if (!errorMessages) {
|
||||||
// If no errorMessages were returned, then validation passed.
|
// If no errorMessages were returned, then validation passed.
|
||||||
@ -260,6 +258,7 @@ HP.ProtocolEngine = class ProtocolEngine {
|
|||||||
OHIF.log.info('ProtocolEngine::findMatchByStudy');
|
OHIF.log.info('ProtocolEngine::findMatchByStudy');
|
||||||
|
|
||||||
const matched = [];
|
const matched = [];
|
||||||
|
const studyInstance = study.getFirstInstance();
|
||||||
|
|
||||||
HP.ProtocolStore.getProtocol().forEach(protocol => {
|
HP.ProtocolStore.getProtocol().forEach(protocol => {
|
||||||
// Clone the protocol's protocolMatchingRules array
|
// Clone the protocol's protocolMatchingRules array
|
||||||
@ -271,18 +270,26 @@ HP.ProtocolEngine = class ProtocolEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set custom attribute for study metadata
|
// Set custom attribute for study metadata
|
||||||
const numberOfPriorsReferenced = this.getNumberOfAvailablePriors(study.getStudyInstanceUID());
|
const numberOfPriorsReferenced = this.getNumberOfAvailablePriors(study.getObjectID());
|
||||||
study.setCustomAttribute('numberOfPriorsReferenced', numberOfPriorsReferenced);
|
studyInstance.setCustomAttribute('numberOfPriorsReferenced', numberOfPriorsReferenced);
|
||||||
|
|
||||||
const rule = new HP.ProtocolMatchingRule('numberOfPriorsReferenced', {
|
const requiredPriorsRule = new HP.ProtocolMatchingRule('numberOfPriorsReferenced', {
|
||||||
numericality: {
|
numericality: {
|
||||||
greaterThanOrEqualTo: protocol.numberOfPriorsReferenced
|
greaterThanOrEqualTo: protocol.numberOfPriorsReferenced
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
rules.push(rule);
|
rules.push(requiredPriorsRule);
|
||||||
|
|
||||||
const matchedDetails = HP.match(study, rules);
|
const exactPriorsDesiredRule = new HP.ProtocolMatchingRule('numberOfPriorsReferenced', {
|
||||||
|
numericality: {
|
||||||
|
equalTo: protocol.numberOfPriorsReferenced
|
||||||
|
},
|
||||||
|
}, false, 1);
|
||||||
|
|
||||||
|
rules.push(exactPriorsDesiredRule);
|
||||||
|
|
||||||
|
const matchedDetails = HP.match(studyInstance, rules);
|
||||||
|
|
||||||
if (matchedDetails.score > 0) {
|
if (matchedDetails.score > 0) {
|
||||||
matched.push({
|
matched.push({
|
||||||
@ -366,25 +373,48 @@ HP.ProtocolEngine = class ProtocolEngine {
|
|||||||
/**
|
/**
|
||||||
* Get the number of prior studies supplied in the priorStudies map property.
|
* Get the number of prior studies supplied in the priorStudies map property.
|
||||||
*
|
*
|
||||||
* @param {String} studyInstanceUID The StudyInstanceUID of the study whose priors are needed
|
* @param {String} studyObjectID The study object ID of the study whose priors are needed
|
||||||
* @returns {number} The number of available prior studies with the same PatientID
|
* @returns {number} The number of available prior studies with the same PatientID
|
||||||
*/
|
*/
|
||||||
getNumberOfAvailablePriors(studyInstanceUID) {
|
getNumberOfAvailablePriors(studyObjectID) {
|
||||||
const priors = this.getAvailableStudyPriors(studyInstanceUID);
|
const priors = this.getAvailableStudyPriors(studyObjectID);
|
||||||
return priors.length;
|
return priors.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the array prior studies from a specific study.
|
* Get the array of prior studies from a specific study.
|
||||||
*
|
*
|
||||||
* @param {String} studyInstanceUID The StudyInstanceUID of the study whose priors are needed
|
* @param {String} studyObjectID The study object ID of the study whose priors are needed
|
||||||
* @returns {Array} The array of available priors or an empty array
|
* @returns {Array} The array of available priors or an empty array
|
||||||
*/
|
*/
|
||||||
getAvailableStudyPriors(studyInstanceUID) {
|
getAvailableStudyPriors(studyObjectID) {
|
||||||
const priors = this.priorStudies.get(studyInstanceUID);
|
const priors = this.priorStudies.get(studyObjectID);
|
||||||
return priors instanceof Array ? priors : [];
|
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 = HP.match(prior, protocolMatchingRules);
|
||||||
|
return matchDetails.score > 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return allPriors;
|
||||||
|
}
|
||||||
|
|
||||||
// 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) {
|
||||||
OHIF.log.info('ProtocolEngine::matchImages');
|
OHIF.log.info('ProtocolEngine::matchImages');
|
||||||
@ -393,14 +423,18 @@ HP.ProtocolEngine = class ProtocolEngine {
|
|||||||
|
|
||||||
const matchingScores = [];
|
const matchingScores = [];
|
||||||
const currentStudy = this.studies[0];
|
const currentStudy = this.studies[0];
|
||||||
|
const firstInstance = currentStudy.getFirstInstance();
|
||||||
|
|
||||||
let highestStudyMatchingScore = 0;
|
let highestStudyMatchingScore = 0;
|
||||||
let highestSeriesMatchingScore = 0;
|
let highestSeriesMatchingScore = 0;
|
||||||
let highestImageMatchingScore = 0;
|
let highestImageMatchingScore = 0;
|
||||||
let bestMatch;
|
let bestMatch;
|
||||||
|
|
||||||
// Set custom attribute for study metadata
|
// Set custom attribute for study metadata and it's first instance
|
||||||
currentStudy.setCustomAttribute('abstractPriorValue', 0);
|
currentStudy.setCustomAttribute('abstractPriorValue', 0);
|
||||||
|
if (firstInstance instanceof InstanceMetadata) {
|
||||||
|
firstInstance.setCustomAttribute('abstractPriorValue', 0);
|
||||||
|
}
|
||||||
|
|
||||||
studyMatchingRules.forEach(rule => {
|
studyMatchingRules.forEach(rule => {
|
||||||
if (rule.attribute === 'abstractPriorValue') {
|
if (rule.attribute === 'abstractPriorValue') {
|
||||||
@ -411,7 +445,7 @@ HP.ProtocolEngine = class ProtocolEngine {
|
|||||||
abstractPriorValue = parseInt(abstractPriorValue, 10);
|
abstractPriorValue = parseInt(abstractPriorValue, 10);
|
||||||
// TODO: Restrict or clarify validators for abstractPriorValue?
|
// TODO: Restrict or clarify validators for abstractPriorValue?
|
||||||
|
|
||||||
const studies = this.getAvailableStudyPriors(currentStudy.getStudyInstanceUID());
|
const studies = this.getPriorsByProtocolMatchingRules(currentStudy.getObjectID());
|
||||||
|
|
||||||
// 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?
|
||||||
@ -429,10 +463,10 @@ HP.ProtocolEngine = class ProtocolEngine {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const priorStudyInstanceUID = priorStudy.getStudyInstanceUID();
|
const priorStudyObjectID = priorStudy.getObjectID();
|
||||||
|
|
||||||
// Check if study metadata is already in studies list
|
// Check if study metadata is already in studies list
|
||||||
if (this.studies.find(study => study.getStudyInstanceUID() === priorStudyInstanceUID)) {
|
if (this.studies.find(study => study.getObjectID() === priorStudyObjectID)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -441,6 +475,12 @@ HP.ProtocolEngine = class ProtocolEngine {
|
|||||||
// Set the custom attribute abstractPriorValue for the study metadata
|
// Set the custom attribute abstractPriorValue for the study metadata
|
||||||
studyMetadata.setCustomAttribute('abstractPriorValue', abstractPriorValue);
|
studyMetadata.setCustomAttribute('abstractPriorValue', abstractPriorValue);
|
||||||
|
|
||||||
|
// Also add custom attribute
|
||||||
|
const firstInstance = studyMetadata.getFirstInstance();
|
||||||
|
if (firstInstance instanceof InstanceMetadata) {
|
||||||
|
firstInstance.setCustomAttribute('abstractPriorValue', abstractPriorValue);
|
||||||
|
}
|
||||||
|
|
||||||
// Insert the new study metadata
|
// Insert the new study metadata
|
||||||
this.studies.push(studyMetadata);
|
this.studies.push(studyMetadata);
|
||||||
|
|
||||||
@ -448,14 +488,14 @@ HP.ProtocolEngine = class ProtocolEngine {
|
|||||||
this.updateViewports();
|
this.updateViewports();
|
||||||
}, error => {
|
}, error => {
|
||||||
OHIF.log.warn(error);
|
OHIF.log.warn(error);
|
||||||
throw new OHIFError(`ProtocolEngine::matchImages could not get study metadata for studyInstanceUID: ${priorStudyInstanceUID}`);
|
throw new OHIFError(`ProtocolEngine::matchImages could not get study metadata for the Study with the following ObjectID: ${priorStudyObjectID}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// TODO: Add relative Date / time
|
// TODO: Add relative Date / time
|
||||||
});
|
});
|
||||||
|
|
||||||
this.studies.forEach(study => {
|
this.studies.forEach(study => {
|
||||||
const studyMatchDetails = HP.match(study, studyMatchingRules);
|
const studyMatchDetails = HP.match(study.getFirstInstance(), studyMatchingRules);
|
||||||
if ((studyMatchingRules.length && !studyMatchDetails.score) ||
|
if ((studyMatchingRules.length && !studyMatchDetails.score) ||
|
||||||
studyMatchDetails.score < highestStudyMatchingScore) {
|
studyMatchDetails.score < highestStudyMatchingScore) {
|
||||||
return;
|
return;
|
||||||
@ -464,7 +504,7 @@ HP.ProtocolEngine = class ProtocolEngine {
|
|||||||
highestStudyMatchingScore = studyMatchDetails.score;
|
highestStudyMatchingScore = studyMatchDetails.score;
|
||||||
|
|
||||||
study.forEachSeries(series => {
|
study.forEachSeries(series => {
|
||||||
const seriesMatchDetails = HP.match(series, seriesMatchingRules);
|
const seriesMatchDetails = HP.match(series.getFirstInstance(), seriesMatchingRules);
|
||||||
if ((seriesMatchingRules.length && !seriesMatchDetails.score) ||
|
if ((seriesMatchingRules.length && !seriesMatchDetails.score) ||
|
||||||
seriesMatchDetails.score < highestSeriesMatchingScore) {
|
seriesMatchDetails.score < highestSeriesMatchingScore) {
|
||||||
return;
|
return;
|
||||||
@ -478,7 +518,7 @@ HP.ProtocolEngine = class ProtocolEngine {
|
|||||||
// See https://ohiforg.atlassian.net/browse/LT-227
|
// See https://ohiforg.atlassian.net/browse/LT-227
|
||||||
// sopClassUid = x00080016
|
// sopClassUid = x00080016
|
||||||
// rows = x00280010
|
// rows = x00280010
|
||||||
if (!OHIF.viewerbase.isImage(instance.getRawValue('x00080016')) && !instance.getRawValue('x00280010')) {
|
if (!OHIF.viewerbase.isImage(instance.getTagValue('x00080016')) && !instance.getTagValue('x00280010')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -509,9 +549,9 @@ HP.ProtocolEngine = class ProtocolEngine {
|
|||||||
matchDetails: matchDetails,
|
matchDetails: matchDetails,
|
||||||
sortingInfo: {
|
sortingInfo: {
|
||||||
score: totalMatchScore,
|
score: totalMatchScore,
|
||||||
study: instance.getRawValue('x00080020') + instance.getRawValue('x00080030'), // StudyDate = x00080020 StudyTime = x00080030
|
study: instance.getTagValue('x00080020') + instance.getTagValue('x00080030'), // StudyDate = x00080020 StudyTime = x00080030
|
||||||
series: parseInt(instance.getRawValue('x00200011')), // TODO: change for seriesDateTime SeriesNumber = x00200011
|
series: parseInt(instance.getTagValue('x00200011')), // TODO: change for seriesDateTime SeriesNumber = x00200011
|
||||||
instance: parseInt(instance.getRawValue('x00200013')) // TODO: change for acquisitionTime InstanceNumber = x00200013
|
instance: parseInt(instance.getTagValue('x00200013')) // TODO: change for acquisitionTime InstanceNumber = x00200013
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -147,10 +147,12 @@ var defaultStrategy = (function () {
|
|||||||
|
|
||||||
Meteor.startup(() => {
|
Meteor.startup(() => {
|
||||||
HP.ProtocolStore.setStrategy(defaultStrategy);
|
HP.ProtocolStore.setStrategy(defaultStrategy);
|
||||||
|
|
||||||
HP.ProtocolStore.onReady(() => {
|
HP.ProtocolStore.onReady(() => {
|
||||||
console.log('Inserting default protocols');
|
console.log('Inserting default protocols');
|
||||||
HP.ProtocolStore.addProtocol(HP.defaultProtocol);
|
HP.ProtocolStore.addProtocol(HP.defaultProtocol);
|
||||||
//HP.ProtocolStore.addProtocol(HP.testProtocol);
|
//HP.ProtocolStore.addProtocol(HP.testProtocol);
|
||||||
|
HP.demoProtocols.forEach(protocol => {
|
||||||
|
HP.ProtocolStore.addProtocol(protocol);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -8,8 +8,8 @@ export class OHIFInstanceMetadata extends InstanceMetadata {
|
|||||||
/**
|
/**
|
||||||
* @param {Object} Instance object.
|
* @param {Object} Instance object.
|
||||||
*/
|
*/
|
||||||
constructor(data, series, study) {
|
constructor(data, series, study, uid) {
|
||||||
super(data);
|
super(data, uid);
|
||||||
this.init(series, study);
|
this.init(series, study);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ export class OHIFInstanceMetadata extends InstanceMetadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Override
|
// Override
|
||||||
getRawValue(tagOrProperty, defaultValue, bypassCache) {
|
getTagValue(tagOrProperty, defaultValue, bypassCache) {
|
||||||
|
|
||||||
// check if this property has been cached...
|
// check if this property has been cached...
|
||||||
if (tagOrProperty in this._cache && bypassCache !== true) {
|
if (tagOrProperty in this._cache && bypassCache !== true) {
|
||||||
|
|||||||
@ -6,8 +6,8 @@ export class OHIFSeriesMetadata extends Viewerbase.metadata.SeriesMetadata {
|
|||||||
/**
|
/**
|
||||||
* @param {Object} Series object.
|
* @param {Object} Series object.
|
||||||
*/
|
*/
|
||||||
constructor(data, study) {
|
constructor(data, study, uid) {
|
||||||
super(data);
|
super(data, uid);
|
||||||
this.init(study);
|
this.init(study);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,8 +6,8 @@ export class OHIFStudyMetadata extends Viewerbase.metadata.StudyMetadata {
|
|||||||
/**
|
/**
|
||||||
* @param {Object} Study object.
|
* @param {Object} Study object.
|
||||||
*/
|
*/
|
||||||
constructor(data) {
|
constructor(data, uid) {
|
||||||
super(data);
|
super(data, uid);
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -43,9 +43,9 @@ export class OHIFStudyMetadataSource extends OHIF.viewerbase.StudyMetadataSource
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.getByInstanceUID(study.getStudyInstanceUID()).then(studyInfo => {
|
this.getByInstanceUID(studyInstanceUID).then(studyInfo => {
|
||||||
// Create study metadata object
|
// Create study metadata object
|
||||||
const studyMetadata = new OHIF.metadata.StudyMetadata(studyInfo);
|
const studyMetadata = new OHIF.metadata.StudyMetadata(studyInfo, studyInfo.studyInstanceUid);
|
||||||
|
|
||||||
// Get Study display sets
|
// Get Study display sets
|
||||||
const displaySets = OHIF.viewerbase.sortingManager.getDisplaySets(studyMetadata);
|
const displaySets = OHIF.viewerbase.sortingManager.getDisplaySets(studyMetadata);
|
||||||
|
|||||||
@ -3,27 +3,30 @@ import 'meteor/ohif:viewerbase';
|
|||||||
// Local Dependencies
|
// Local Dependencies
|
||||||
import { OHIFStudySummary } from './OHIFStudySummary';
|
import { OHIFStudySummary } from './OHIFStudySummary';
|
||||||
|
|
||||||
const StudyMetadata = OHIF.viewerbase.metadata.StudyMetadata;
|
const { StudyMetadata, StudySummary } = OHIF.viewerbase.metadata;
|
||||||
const StudySummary = OHIF.viewerbase.metadata.StudySummary;
|
const PATIENT_ID = 'x00100020';
|
||||||
const PatientID = 'x00100020';
|
const STUDY_DATE = 'x00080020';
|
||||||
const StudyDate = 'x00080020';
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the priors of a given study
|
||||||
|
* @param {StudyMetadata|StudySummary} study An instance of StudyMetadata|StudySummary class to get it's priors
|
||||||
|
* @return {Array} An Array of StudySummary objects representing the study priors for the given study or an empty array if none
|
||||||
|
*/
|
||||||
const getStudyPriors = study => {
|
const getStudyPriors = study => {
|
||||||
|
|
||||||
const priorStudies = [];
|
if (!(study instanceof StudyMetadata) && !(study instanceof StudySummary)) {
|
||||||
let patientID;
|
throw new OHIF.viewerbase.OHIFError('getStudyPriors study must be an instance of StudySummary or StudyMetadata');
|
||||||
let studyDate;
|
|
||||||
|
|
||||||
if (study instanceof StudyMetadata) {
|
|
||||||
const instance = study.getFirstInstance();
|
|
||||||
patientID = instance.getRawValue(PatientID); // PatientID
|
|
||||||
studyDate = instance.getRawValue(StudyDate); // StudyDate
|
|
||||||
} else if (study instanceof StudySummary) {
|
|
||||||
patientID = study.getTagValue(PatientID); // PatientID
|
|
||||||
studyDate = study.getTagValue(StudyDate); // StudyDate
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find prior studies in global StudyListStudies Minimongo collection...
|
if (study instanceof StudyMetadata) {
|
||||||
|
study = study.getFirstInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
const priorStudies = [];
|
||||||
|
const patientID = study.getTagValue(PATIENT_ID); // PatientID
|
||||||
|
const studyDate = study.getTagValue(STUDY_DATE); // StudyDate
|
||||||
|
|
||||||
|
// Find prior studies in global StudyListStudies Minimongo collection
|
||||||
const cursor = StudyListStudies.find({
|
const cursor = StudyListStudies.find({
|
||||||
patientId: patientID,
|
patientId: patientID,
|
||||||
studyDate: {
|
studyDate: {
|
||||||
@ -35,10 +38,9 @@ const getStudyPriors = study => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create an OHIFStudySummary object for each prior study found...
|
// Create an OHIFStudySummary object for each prior study found
|
||||||
cursor.forEach(study => {
|
cursor.forEach(study => {
|
||||||
const summary = new OHIFStudySummary();
|
const summary = new OHIFStudySummary(study, null, study.studyInstanceUid);
|
||||||
summary.addTags(study);
|
|
||||||
priorStudies.push(summary);
|
priorStudies.push(summary);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -3,8 +3,7 @@ import 'meteor/ohif:viewerbase';
|
|||||||
// Local Dependencies
|
// Local Dependencies
|
||||||
import { getStudyPriors } from './getStudyPriors';
|
import { getStudyPriors } from './getStudyPriors';
|
||||||
|
|
||||||
const StudyMetadata = OHIF.viewerbase.metadata.StudyMetadata;
|
const { StudyMetadata, StudySummary } = OHIF.viewerbase.metadata;
|
||||||
const StudySummary = OHIF.viewerbase.metadata.StudySummary;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a Map of study priors where the key of each entry is the StudyInstanceUID and its value is an array of StudySummary instances.
|
* Create a Map of study priors where the key of each entry is the StudyInstanceUID and its value is an array of StudySummary instances.
|
||||||
@ -17,10 +16,10 @@ const getStudyPriorsMap = studies => {
|
|||||||
if (studies instanceof Array) {
|
if (studies instanceof Array) {
|
||||||
studies.forEach(study => {
|
studies.forEach(study => {
|
||||||
if (study instanceof StudyMetadata || study instanceof StudySummary) {
|
if (study instanceof StudyMetadata || study instanceof StudySummary) {
|
||||||
const studyInstanceUID = study.getStudyInstanceUID();
|
const studyObjectID = study.getObjectID();
|
||||||
if (studyInstanceUID) {
|
if (studyObjectID) {
|
||||||
const priors = getStudyPriors(study);
|
const priors = getStudyPriors(study);
|
||||||
priorsMap.set(studyInstanceUID, priors);
|
priorsMap.set(studyObjectID, priors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -130,7 +130,7 @@ const loadDisplaySetIntoViewport = (data, templateData) => {
|
|||||||
seriesInstanceUid,
|
seriesInstanceUid,
|
||||||
displaySetInstanceUid,
|
displaySetInstanceUid,
|
||||||
currentImageIdIndex,
|
currentImageIdIndex,
|
||||||
viewport,
|
viewport: viewport || data.viewport,
|
||||||
viewportIndex
|
viewportIndex
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -235,7 +235,7 @@ const loadDisplaySetIntoViewport = (data, templateData) => {
|
|||||||
|
|
||||||
// Call the handler function that represents the end of the image loading phase
|
// Call the handler function that represents the end of the image loading phase
|
||||||
// (e.g. hide the progress text box)
|
// (e.g. hide the progress text box)
|
||||||
endLoadingHandler(element);
|
endLoadingHandler(element, image);
|
||||||
|
|
||||||
// Remove the 'empty' class from the viewport to hide any instruction text
|
// Remove the 'empty' class from the viewport to hide any instruction text
|
||||||
element.classList.remove('empty');
|
element.classList.remove('empty');
|
||||||
|
|||||||
@ -1,7 +1,12 @@
|
|||||||
import { Meteor } from 'meteor/meteor';
|
import { Meteor } from 'meteor/meteor';
|
||||||
|
import { Session } from 'meteor/session';
|
||||||
|
|
||||||
import { OHIF } from 'meteor/ohif:core';
|
import { OHIF } from 'meteor/ohif:core';
|
||||||
import { viewportUtils } from './viewportUtils';
|
import { viewportUtils } from './viewportUtils';
|
||||||
|
|
||||||
|
const WL_PRESET_CUSTOM = 'Custom';
|
||||||
|
const WL_PRESET_DEFAULT = 'Default';
|
||||||
|
|
||||||
// TODO: add this to a namespace definition
|
// TODO: add this to a namespace definition
|
||||||
Meteor.startup(function() {
|
Meteor.startup(function() {
|
||||||
OHIF.viewer.defaultWLPresets = {
|
OHIF.viewer.defaultWLPresets = {
|
||||||
@ -27,30 +32,75 @@ Meteor.startup(function() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// For now
|
setOHIFWLPresets(OHIF.viewer.defaultWLPresets);
|
||||||
OHIF.viewer.wlPresets = OHIF.viewer.defaultWLPresets;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function applyWLPreset(presetName, element) {
|
function updateElementWLPresetData(element) {
|
||||||
OHIF.log.info('Applying WL Preset: ' + presetName);
|
const wlPresetData = cornerstone.getElementData(element, 'wlPreset');
|
||||||
if (presetName !== 'Custom') {
|
const enabledElement = cornerstone.getEnabledElement(element);
|
||||||
const viewport = cornerstone.getViewport(element);
|
const { viewport, image } = enabledElement;
|
||||||
|
const { windowCenter, windowWidth } = viewport.voi;
|
||||||
|
let presetName;
|
||||||
|
|
||||||
if (presetName === 'Default') {
|
if (windowWidth === image.windowWidth && windowCenter === image.windowCenter) {
|
||||||
const enabledElement = cornerstone.getEnabledElement(element);
|
presetName = WL_PRESET_DEFAULT;
|
||||||
viewport.voi.windowWidth = enabledElement.image.windowWidth;
|
} else {
|
||||||
viewport.voi.windowCenter = enabledElement.image.windowCenter;
|
const WLPresets = OHIF.viewer.wlPresets;
|
||||||
} else {
|
for (let name in WLPresets) {
|
||||||
const preset = OHIF.viewer.wlPresets[presetName];
|
const preset = WLPresets[name];
|
||||||
if(preset) {
|
if (windowCenter === preset.wc && windowWidth === preset.ww) {
|
||||||
viewport.voi.windowWidth = preset.ww;
|
presetName = name;
|
||||||
viewport.voi.windowCenter = preset.wc;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the viewport
|
|
||||||
cornerstone.setViewport(element, viewport);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wlPresetData.name = presetName || WL_PRESET_CUSTOM;
|
||||||
|
wlPresetData.ww = windowWidth;
|
||||||
|
wlPresetData.wc = windowCenter;
|
||||||
|
|
||||||
|
if (wlPresetData.name === WL_PRESET_CUSTOM) {
|
||||||
|
const custom = wlPresetData.custom || (wlPresetData.custom = Object.create(null));
|
||||||
|
custom.ww = windowWidth;
|
||||||
|
custom.wc = windowCenter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set specified W/L preset on given element on fallback to default W/L preset if the specified preset is not valid.
|
||||||
|
* @param {String} presetName The desired W/L preset to be applied
|
||||||
|
* @param {DOMElement} element An enabled viewport DOM Element.
|
||||||
|
*/
|
||||||
|
function applyWLPreset(presetName, element) {
|
||||||
|
const wlPresets = OHIF.viewer.wlPresets;
|
||||||
|
const wlPresetData = cornerstone.getElementData(element, 'wlPreset');
|
||||||
|
const viewport = cornerstone.getViewport(element);
|
||||||
|
|
||||||
|
if (presetName === WL_PRESET_CUSTOM && wlPresetData.custom) {
|
||||||
|
viewport.voi.windowWidth = wlPresetData.custom.ww;
|
||||||
|
viewport.voi.windowCenter = wlPresetData.custom.wc;
|
||||||
|
} else if (presetName in wlPresets) {
|
||||||
|
const preset = wlPresets[presetName];
|
||||||
|
viewport.voi.windowWidth = preset.ww;
|
||||||
|
viewport.voi.windowCenter = preset.wc;
|
||||||
|
} else {
|
||||||
|
const enabledElement = cornerstone.getEnabledElement(element);
|
||||||
|
viewport.voi.windowWidth = enabledElement.image.windowWidth;
|
||||||
|
viewport.voi.windowCenter = enabledElement.image.windowCenter;
|
||||||
|
presetName = WL_PRESET_DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
wlPresetData.name = presetName;
|
||||||
|
wlPresetData.ww = viewport.voi.windowWidth;
|
||||||
|
wlPresetData.wc = viewport.voi.windowCenter;
|
||||||
|
|
||||||
|
// Update the viewport
|
||||||
|
cornerstone.setViewport(element, viewport);
|
||||||
|
|
||||||
|
OHIF.log.info('WLPresets::Applying WL Preset: ' + presetName);
|
||||||
|
|
||||||
|
// Notify other components about W/L Preset changes
|
||||||
|
Session.set('OHIFWlPresetApplied', presetName);
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyWLPresetToActiveElement(presetName) {
|
function applyWLPresetToActiveElement(presetName) {
|
||||||
@ -60,9 +110,6 @@ function applyWLPresetToActiveElement(presetName) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
applyWLPreset(presetName, element);
|
applyWLPreset(presetName, element);
|
||||||
|
|
||||||
// To perform reactivity in other components
|
|
||||||
Session.set('WLPresetActiveElement', presetName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -70,7 +117,14 @@ function applyWLPresetToActiveElement(presetName) {
|
|||||||
* @param {Object} wlPresets Object with wlPresets mapping
|
* @param {Object} wlPresets Object with wlPresets mapping
|
||||||
*/
|
*/
|
||||||
function setOHIFWLPresets(wlPresets) {
|
function setOHIFWLPresets(wlPresets) {
|
||||||
OHIF.viewer.wlPresets = wlPresets;
|
const hasOwn = Object.prototype.hasOwnProperty;
|
||||||
|
const presetMap = Object.create(null); // Objects without prototype have much faster lookup times
|
||||||
|
for (let name in wlPresets) {
|
||||||
|
if (hasOwn.call(wlPresets, name)) {
|
||||||
|
presetMap[name] = wlPresets[name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OHIF.viewer.wlPresets = presetMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -80,7 +134,8 @@ function setOHIFWLPresets(wlPresets) {
|
|||||||
const WLPresets = {
|
const WLPresets = {
|
||||||
applyWLPreset,
|
applyWLPreset,
|
||||||
applyWLPresetToActiveElement,
|
applyWLPresetToActiveElement,
|
||||||
setOHIFWLPresets
|
setOHIFWLPresets,
|
||||||
|
updateElementWLPresetData
|
||||||
};
|
};
|
||||||
|
|
||||||
export { WLPresets };
|
export { WLPresets };
|
||||||
|
|||||||
@ -1,14 +1,21 @@
|
|||||||
import { Metadata } from './Metadata';
|
import { Metadata } from './Metadata';
|
||||||
import { OHIFError } from '../OHIFError';
|
import { OHIFError } from '../OHIFError';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ATTENTION! This class should never depend on StudyMetadata or SeriesMetadata classes as this could
|
||||||
|
* possibly cause circular dependency issues.
|
||||||
|
*/
|
||||||
|
|
||||||
const UNDEFINED = 'undefined';
|
const UNDEFINED = 'undefined';
|
||||||
const NUMBER = 'number';
|
const NUMBER = 'number';
|
||||||
const STRING = 'string';
|
const STRING = 'string';
|
||||||
|
const STUDY_INSTANCE_UID = 'x0020000d';
|
||||||
|
const SERIES_INSTANCE_UID = 'x0020000e';
|
||||||
|
|
||||||
export class InstanceMetadata extends Metadata {
|
export class InstanceMetadata extends Metadata {
|
||||||
|
|
||||||
constructor(data) {
|
constructor(data, uid) {
|
||||||
super(data);
|
super(data, uid);
|
||||||
// Initialize Private Properties
|
// Initialize Private Properties
|
||||||
Object.defineProperties(this, {
|
Object.defineProperties(this, {
|
||||||
_sopInstanceUID: {
|
_sopInstanceUID: {
|
||||||
@ -60,6 +67,20 @@ export class InstanceMetadata extends Metadata {
|
|||||||
* Public Methods
|
* Public Methods
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the StudyInstanceUID of the current instance. This method is basically a shorthand the full "getTagValue" method call.
|
||||||
|
*/
|
||||||
|
getStudyInstanceUID() {
|
||||||
|
return this.getTagValue(STUDY_INSTANCE_UID, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the SeriesInstanceUID of the current instance. This method is basically a shorthand the full "getTagValue" method call.
|
||||||
|
*/
|
||||||
|
getSeriesInstanceUID() {
|
||||||
|
return this.getTagValue(SERIES_INSTANCE_UID, null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the SOPInstanceUID of the current instance.
|
* Returns the SOPInstanceUID of the current instance.
|
||||||
*/
|
*/
|
||||||
@ -69,7 +90,7 @@ export class InstanceMetadata extends Metadata {
|
|||||||
|
|
||||||
// @TODO: Improve this... (E.g.: blob data)
|
// @TODO: Improve this... (E.g.: blob data)
|
||||||
getStringValue(tagOrProperty, index, defaultValue) {
|
getStringValue(tagOrProperty, index, defaultValue) {
|
||||||
let value = this.getRawValue(tagOrProperty, defaultValue);
|
let value = this.getTagValue(tagOrProperty, defaultValue);
|
||||||
|
|
||||||
if (typeof value !== STRING && typeof value !== UNDEFINED) {
|
if (typeof value !== STRING && typeof value !== UNDEFINED) {
|
||||||
value = value.toString();
|
value = value.toString();
|
||||||
@ -80,7 +101,7 @@ export class InstanceMetadata extends Metadata {
|
|||||||
|
|
||||||
// @TODO: Improve this... (E.g.: blob data)
|
// @TODO: Improve this... (E.g.: blob data)
|
||||||
getFloatValue(tagOrProperty, index, defaultValue) {
|
getFloatValue(tagOrProperty, index, defaultValue) {
|
||||||
let value = this.getRawValue(tagOrProperty, defaultValue);
|
let value = this.getTagValue(tagOrProperty, defaultValue);
|
||||||
value = InstanceMetadata.getIndexedValue(value, index, defaultValue);
|
value = InstanceMetadata.getIndexedValue(value, index, defaultValue);
|
||||||
|
|
||||||
if(value instanceof Array) {
|
if(value instanceof Array) {
|
||||||
@ -96,7 +117,7 @@ export class InstanceMetadata extends Metadata {
|
|||||||
|
|
||||||
// @TODO: Improve this... (E.g.: blob data)
|
// @TODO: Improve this... (E.g.: blob data)
|
||||||
getIntValue(tagOrProperty, index, defaultValue) {
|
getIntValue(tagOrProperty, index, defaultValue) {
|
||||||
let value = this.getRawValue(tagOrProperty, defaultValue);
|
let value = this.getTagValue(tagOrProperty, defaultValue);
|
||||||
value = InstanceMetadata.getIndexedValue(value, index, defaultValue);
|
value = InstanceMetadata.getIndexedValue(value, index, defaultValue);
|
||||||
|
|
||||||
if(value instanceof Array) {
|
if(value instanceof Array) {
|
||||||
@ -111,13 +132,20 @@ export class InstanceMetadata extends Metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* @deprecated Please use getTagValue instead.
|
||||||
*/
|
*/
|
||||||
getRawValue(tagOrProperty, defaultValue) {
|
getRawValue(tagOrProperty, defaultValue) {
|
||||||
|
return this.getTagValue(tagOrProperty, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function should be overriden by specialized classes in order to allow client libraries or viewers to take advantage of the Study Metadata API.
|
||||||
|
*/
|
||||||
|
getTagValue(tagOrProperty, defaultValue) {
|
||||||
/**
|
/**
|
||||||
* Please override this method on a specialized class.
|
* Please override this method on a specialized class.
|
||||||
*/
|
*/
|
||||||
throw new OHIFError('InstanceMetadata::getRawValue is not overriden. Please, override it in a specialized class. See OHIFInstanceMetadata for example');
|
throw new OHIFError('InstanceMetadata::getTagValue is not overriden. Please, override it in a specialized class. See OHIFInstanceMetadata for example');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -8,13 +8,17 @@ const NUMBER = 'number';
|
|||||||
const FUNCTION = 'function';
|
const FUNCTION = 'function';
|
||||||
const OBJECT = 'object';
|
const OBJECT = 'object';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Definition
|
||||||
|
*/
|
||||||
|
|
||||||
export class Metadata {
|
export class Metadata {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor and Instance Methods
|
* Constructor and Instance Methods
|
||||||
*/
|
*/
|
||||||
|
|
||||||
constructor(data) {
|
constructor(data, uid) {
|
||||||
// Define the main "_data" private property as an immutable property.
|
// Define the main "_data" private property as an immutable property.
|
||||||
// IMPORTANT: This property can only be set during instance construction.
|
// IMPORTANT: This property can only be set during instance construction.
|
||||||
Object.defineProperty(this, '_data', {
|
Object.defineProperty(this, '_data', {
|
||||||
@ -24,7 +28,16 @@ export class Metadata {
|
|||||||
value: data
|
value: data
|
||||||
});
|
});
|
||||||
|
|
||||||
// Define "_custom" properties as an immutable property
|
// Define the main "_uid" private property as an immutable property.
|
||||||
|
// IMPORTANT: This property can only be set during instance construction.
|
||||||
|
Object.defineProperty(this, '_uid', {
|
||||||
|
configurable: false,
|
||||||
|
enumerable: false,
|
||||||
|
writable: false,
|
||||||
|
value: uid
|
||||||
|
});
|
||||||
|
|
||||||
|
// Define "_custom" properties as an immutable property.
|
||||||
// IMPORTANT: This property can only be set during instance construction.
|
// IMPORTANT: This property can only be set during instance construction.
|
||||||
Object.defineProperty(this, '_custom', {
|
Object.defineProperty(this, '_custom', {
|
||||||
configurable: false,
|
configurable: false,
|
||||||
@ -47,6 +60,13 @@ export class Metadata {
|
|||||||
return propertyValue;
|
return propertyValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get unique object ID
|
||||||
|
*/
|
||||||
|
getObjectID() {
|
||||||
|
return this._uid;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set custom attribute value
|
* Set custom attribute value
|
||||||
* @param {String} attribute Custom attribute name
|
* @param {String} attribute Custom attribute name
|
||||||
@ -74,6 +94,20 @@ export class Metadata {
|
|||||||
return attribute in this._custom;
|
return attribute in this._custom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set custom attributes in batch mode.
|
||||||
|
* @param {Object} attributeMap An object whose own properties will be used as custom attributes.
|
||||||
|
*/
|
||||||
|
setCustomAttributes(attributeMap) {
|
||||||
|
const _hasOwn = Object.prototype.hasOwnProperty;
|
||||||
|
const _custom = this._custom;
|
||||||
|
for (let attribute in attributeMap) {
|
||||||
|
if (_hasOwn.call(attributeMap, attribute)) {
|
||||||
|
_custom[attribute] = attributeMap[attribute];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static Methods
|
* Static Methods
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -3,8 +3,8 @@ import { InstanceMetadata } from './InstanceMetadata';
|
|||||||
|
|
||||||
export class SeriesMetadata extends Metadata {
|
export class SeriesMetadata extends Metadata {
|
||||||
|
|
||||||
constructor(data) {
|
constructor(data, uid) {
|
||||||
super(data);
|
super(data, uid);
|
||||||
// Initialize Private Properties
|
// Initialize Private Properties
|
||||||
Object.defineProperties(this, {
|
Object.defineProperties(this, {
|
||||||
_seriesInstanceUID: {
|
_seriesInstanceUID: {
|
||||||
@ -18,6 +18,12 @@ export class SeriesMetadata extends Metadata {
|
|||||||
enumerable: false,
|
enumerable: false,
|
||||||
writable: false,
|
writable: false,
|
||||||
value: []
|
value: []
|
||||||
|
},
|
||||||
|
_firstInstance: {
|
||||||
|
configurable: false,
|
||||||
|
enumerable: false,
|
||||||
|
writable: true,
|
||||||
|
value: null
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// Initialize Public Properties
|
// Initialize Public Properties
|
||||||
@ -77,6 +83,23 @@ export class SeriesMetadata extends Metadata {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the first instance of the current series retaining a consistent result across multiple calls.
|
||||||
|
* @return {InstanceMetadata} An instance of the InstanceMetadata class or null if it does not exist.
|
||||||
|
*/
|
||||||
|
getFirstInstance() {
|
||||||
|
let instance = this._firstInstance;
|
||||||
|
if (!(instance instanceof InstanceMetadata)) {
|
||||||
|
instance = null;
|
||||||
|
const found = this.getInstanceByIndex(0);
|
||||||
|
if (found instanceof InstanceMetadata) {
|
||||||
|
this._firstInstance = found;
|
||||||
|
instance = found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find an instance by index.
|
* Find an instance by index.
|
||||||
* @param {number} index An integer representing a list index.
|
* @param {number} index An integer representing a list index.
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
import { Metadata } from './Metadata';
|
import { Metadata } from './Metadata';
|
||||||
import { SeriesMetadata } from './SeriesMetadata';
|
import { SeriesMetadata } from './SeriesMetadata';
|
||||||
|
import { InstanceMetadata } from './InstanceMetadata';
|
||||||
import { ImageSet } from '../ImageSet';
|
import { ImageSet } from '../ImageSet';
|
||||||
import { OHIFError } from '../OHIFError';
|
import { OHIFError } from '../OHIFError';
|
||||||
|
|
||||||
export class StudyMetadata extends Metadata {
|
export class StudyMetadata extends Metadata {
|
||||||
|
|
||||||
constructor(data) {
|
constructor(data, uid) {
|
||||||
super(data);
|
super(data, uid);
|
||||||
// Initialize Private Properties
|
// Initialize Private Properties
|
||||||
Object.defineProperties(this, {
|
Object.defineProperties(this, {
|
||||||
_studyInstanceUID: {
|
_studyInstanceUID: {
|
||||||
@ -26,6 +27,18 @@ export class StudyMetadata extends Metadata {
|
|||||||
enumerable: false,
|
enumerable: false,
|
||||||
writable: false,
|
writable: false,
|
||||||
value: []
|
value: []
|
||||||
|
},
|
||||||
|
_firstSeries: {
|
||||||
|
configurable: false,
|
||||||
|
enumerable: false,
|
||||||
|
writable: true,
|
||||||
|
value: null
|
||||||
|
},
|
||||||
|
_firstInstance: {
|
||||||
|
configurable: false,
|
||||||
|
enumerable: false,
|
||||||
|
writable: true,
|
||||||
|
value: null
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// Initialize Public Properties
|
// Initialize Public Properties
|
||||||
@ -232,23 +245,13 @@ export class StudyMetadata extends Metadata {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* It sorts the series based on display sets order. Each series must be an instance
|
* 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.
|
* 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:
|
* Useful example of usage:
|
||||||
* Study data provided by backend does not sort series at all and client-side
|
* 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.
|
* needs series sorted by the same criteria used for sorting display sets.
|
||||||
*/
|
*/
|
||||||
sortSeriesByDisplaySets() {
|
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
|
// Object for mapping display sets' index by seriesInstanceUid
|
||||||
const displaySetsMapping = {};
|
const displaySetsMapping = {};
|
||||||
|
|
||||||
@ -299,18 +302,40 @@ export class StudyMetadata extends Metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get first instance of the first series
|
* Get the first series of the current study retaining a consistent result across multiple calls.
|
||||||
* @return {InstanceMetadata} InstanceMetadata class object or undefined if it doenst exist
|
* @return {SeriesMetadata} An instance of the SeriesMetadata class or null if it does not exist.
|
||||||
|
*/
|
||||||
|
getFirstSeries() {
|
||||||
|
let series = this._firstSeries;
|
||||||
|
if (!(series instanceof SeriesMetadata)) {
|
||||||
|
series = null;
|
||||||
|
const found = this.getSeriesByIndex(0);
|
||||||
|
if (found instanceof SeriesMetadata) {
|
||||||
|
this._firstSeries = found;
|
||||||
|
series = found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return series;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the first instance of the current study retaining a consistent result across multiple calls.
|
||||||
|
* @return {InstanceMetadata} An instance of the InstanceMetadata class or null if it does not exist.
|
||||||
*/
|
*/
|
||||||
getFirstInstance() {
|
getFirstInstance() {
|
||||||
let firstInstance;
|
let instance = this._firstInstance;
|
||||||
const firstSeries = this.getSeriesByIndex(0);
|
if (!(instance instanceof InstanceMetadata)) {
|
||||||
|
instance = null;
|
||||||
if (firstSeries) {
|
const firstSeries = this.getFirstSeries();
|
||||||
firstInstance = firstSeries.getInstanceByIndex(0);
|
if (firstSeries instanceof SeriesMetadata) {
|
||||||
|
const found = firstSeries.getFirstInstance();
|
||||||
|
if (found instanceof InstanceMetadata) {
|
||||||
|
this._firstInstance = found;
|
||||||
|
instance = found;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return instance;
|
||||||
return firstInstance;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,26 +1,24 @@
|
|||||||
|
import { Metadata } from './Metadata';
|
||||||
|
import { OHIFError } from '../OHIFError';
|
||||||
import { DICOMTagDescriptions } from '../../DICOMTagDescriptions';
|
import { DICOMTagDescriptions } from '../../DICOMTagDescriptions';
|
||||||
|
|
||||||
const StudyInstanceUID = 'x0020000d';
|
/**
|
||||||
|
* Constants
|
||||||
|
*/
|
||||||
|
|
||||||
export class StudySummary {
|
const STUDY_INSTANCE_UID = 'x0020000d';
|
||||||
|
|
||||||
constructor(tagMap, propertyMap) {
|
/**
|
||||||
|
* Class Definition
|
||||||
|
*/
|
||||||
|
|
||||||
// Define the main immutable "_data" private property.
|
export class StudySummary extends Metadata {
|
||||||
Object.defineProperties(this, {
|
|
||||||
_tags: {
|
constructor(tagMap, attributeMap, uid) {
|
||||||
configurable: false,
|
|
||||||
enumerable: false,
|
// Call the superclass constructor passing an plain object with no prototype to be used as the main "_data" attribute.
|
||||||
writable: false,
|
const _data = Object.create(null);
|
||||||
value: Object.create(null)
|
super(_data, uid);
|
||||||
},
|
|
||||||
_properties: {
|
|
||||||
configurable: false,
|
|
||||||
enumerable: false,
|
|
||||||
writable: false,
|
|
||||||
value: Object.create(null)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initialize internal tag map if first argument is given.
|
// Initialize internal tag map if first argument is given.
|
||||||
if (tagMap !== void 0) {
|
if (tagMap !== void 0) {
|
||||||
@ -28,67 +26,53 @@ export class StudySummary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize internal property map if second argument is given.
|
// Initialize internal property map if second argument is given.
|
||||||
if (propertyMap !== void 0) {
|
if (attributeMap !== void 0) {
|
||||||
this.addProperties(addProperties);
|
this.setCustomAttributes(attributeMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getStudyInstanceUID() {
|
getStudyInstanceUID() {
|
||||||
// This method should return null if StudyInstanceUID is not available to keep compatibility StudyMetadata API
|
// This method should return null if StudyInstanceUID is not available to keep compatibility StudyMetadata API
|
||||||
return this.getTagValue(StudyInstanceUID) || null;
|
return this.getTagValue(STUDY_INSTANCE_UID) || null;
|
||||||
}
|
|
||||||
|
|
||||||
addProperties(propertyMap) {
|
|
||||||
const _hasOwn = Object.prototype.hasOwnProperty;
|
|
||||||
const _properties = this._properties;
|
|
||||||
for (let property in propertyMap) {
|
|
||||||
if (_hasOwn.call(propertyMap, property)) {
|
|
||||||
_properties[property] = propertyMap[property];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append tags to internal tag map.
|
||||||
|
* @param {Object} tagMap An object whose own properties will be used as tag values and appended to internal tag map.
|
||||||
|
*/
|
||||||
addTags(tagMap) {
|
addTags(tagMap) {
|
||||||
const _hasOwn = Object.prototype.hasOwnProperty;
|
const _hasOwn = Object.prototype.hasOwnProperty;
|
||||||
const _tags = this._tags;
|
const _data = this._data;
|
||||||
for (let tag in tagMap) {
|
for (let tag in tagMap) {
|
||||||
if (_hasOwn.call(tagMap, tag)) {
|
if (_hasOwn.call(tagMap, tag)) {
|
||||||
const description = DICOMTagDescriptions.find(tag);
|
const description = DICOMTagDescriptions.find(tag);
|
||||||
// When a description is available, use its tag as internal key...
|
// When a description is available, use its tag as internal key...
|
||||||
if (description) {
|
if (description) {
|
||||||
_tags[description.tag] = tagMap[tag];
|
_data[description.tag] = tagMap[tag];
|
||||||
} else {
|
} else {
|
||||||
_tags[tag] = tagMap[tag];
|
_data[tag] = tagMap[tag];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tagExists(tagName) {
|
tagExists(tagName) {
|
||||||
const _tags = this._tags;
|
const _data = this._data;
|
||||||
const description = DICOMTagDescriptions.find(tagName);
|
const description = DICOMTagDescriptions.find(tagName);
|
||||||
if (description) {
|
if (description) {
|
||||||
return (description.tag in _tags);
|
return (description.tag in _data);
|
||||||
}
|
}
|
||||||
return (tagName in _tags);
|
return (tagName in _data);
|
||||||
}
|
}
|
||||||
|
|
||||||
getTagValue(tagName) {
|
getTagValue(tagName) {
|
||||||
const _tags = this._tags;
|
const _data = this._data;
|
||||||
const description = DICOMTagDescriptions.find(tagName);
|
const description = DICOMTagDescriptions.find(tagName);
|
||||||
if (description) {
|
if (description) {
|
||||||
return _tags[description.tag];
|
return _data[description.tag];
|
||||||
}
|
}
|
||||||
return _tags[tagName];
|
return _data[tagName];
|
||||||
}
|
|
||||||
|
|
||||||
propertyExists(propertyName) {
|
|
||||||
return (propertyName in this._properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
getPropertyValue(propertyName) {
|
|
||||||
return this._properties[propertyName];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user