First pass at a Hanging Protocol implementation and editor (LT-131)
This commit is contained in:
1 parent
dfcb33153f
commit
2e52d9e3c1
157 files changed
+8869
-23585
No files matched your search
@@ -0,0 +1,15 @@
|
||||
Meteor.publish('hangingprotocols', function() {
|
||||
// TODO: filter by availableTo user
|
||||
return HangingProtocols.find();
|
||||
});
|
||||
|
||||
Meteor.startup(function() {
|
||||
// Uncomment this next line to reset all your Protocols on every server reset
|
||||
// HangingProtocols.remove({});
|
||||
|
||||
if (HangingProtocols.find().count() === 0) {
|
||||
console.log('Inserting default protocols');
|
||||
HangingProtocols.insert(HP.defaultProtocol);
|
||||
HangingProtocols.insert(HP.testProtocol);
|
||||
}
|
||||
});
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,78 +0,0 @@
|
||||
/**
|
||||
* converts an implicit dataSet to a javascript object with the help of a data dictionary
|
||||
*
|
||||
* @param dataSet
|
||||
* @param options
|
||||
*/
|
||||
dataSetToJsWithDictionary = function(dataSet, dictionary, options) {
|
||||
if (!dataSet) {
|
||||
throw 'dataSetToJsWithDictionary: missing required parameter dataSet';
|
||||
}
|
||||
|
||||
options = options || {
|
||||
omitPrivateAttibutes: true, // true if private elements should be omitted
|
||||
maxElementLength: 128 // maximum element length to try and convert to string format
|
||||
};
|
||||
|
||||
// For the purpose of inserting a comma into the tag in order
|
||||
// to search the dictionary, store a position value
|
||||
var position = 3;
|
||||
|
||||
var result = {};
|
||||
|
||||
for (var tag in dataSet.elements) {
|
||||
var element = dataSet.elements[tag];
|
||||
|
||||
// Reformat the tag from x00020010 to "0002,0010" to suit the dictionary
|
||||
var tagFormatted = tagFormatted.substr(1, position) + ',' + tagFormatted.substr(position);
|
||||
|
||||
// Retrieve the tag dictionary entry
|
||||
var tagDictionaryEntry = dictionary[tagFormatted];
|
||||
|
||||
// If there is no tag dictionary entry, or no name
|
||||
// for this tag, just use the original tag instead
|
||||
var tagName;
|
||||
if (!tagDictionaryEntry || !tagDictionaryEntry.name) {
|
||||
tagName = tag;
|
||||
} else {
|
||||
tagName = tagDictionaryEntry.name;
|
||||
}
|
||||
|
||||
// Set the element VR from the dictionary, if necessary and possible
|
||||
if (!element.vr && tagDictionaryEntry && tagDictionaryEntry.vr) {
|
||||
element.vr = tagDictionaryEntry.vr;
|
||||
}
|
||||
|
||||
// skip this element if it a private element and our options specify that we should
|
||||
if (options.omitPrivateAttibutes === true && dicomParser.isPrivateTag(tag)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (element.items) {
|
||||
// handle sequences
|
||||
var sequenceItems = [];
|
||||
for (var i = 0; i < element.items.length; i++) {
|
||||
sequenceItems.push(dicomParser.dataSetToJsWithDictionary(element.items[i].dataSet, dictionary, options));
|
||||
}
|
||||
|
||||
result[tagName] = sequenceItems;
|
||||
} else {
|
||||
var asString;
|
||||
|
||||
if (element.length < options.maxElementLength) {
|
||||
asString = dicomParser.explicitElementToString(dataSet, element);
|
||||
}
|
||||
|
||||
if (asString !== undefined) {
|
||||
result[tagName] = asString;
|
||||
} else {
|
||||
result[tagName] = {
|
||||
dataOffset: element.dataOffset,
|
||||
length: element.length
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
@@ -1,20 +0,0 @@
|
||||
DICOMHP.imageSet = function(setNumber, category) {
|
||||
this.setNumber = setNumber;
|
||||
this.category = category;
|
||||
};
|
||||
|
||||
DICOMHP.imageSet.prototype.setRelativeTime = function(time) {
|
||||
this.relativeTime = time;
|
||||
};
|
||||
|
||||
DICOMHP.imageSet.prototype.setTimeUnits = function(units) {
|
||||
this.timeUnits = units;
|
||||
};
|
||||
|
||||
DICOMHP.imageSet.prototype.setPriorValue = function(priorValue) {
|
||||
this.priorValue = priorValue;
|
||||
};
|
||||
|
||||
DICOMHP.imageSet.prototype.retrieve = function(studyInstanceId) {
|
||||
|
||||
};
|
||||
@@ -1,73 +0,0 @@
|
||||
var valueRepresentationTypes = {
|
||||
number: [
|
||||
'SH',
|
||||
'LO'
|
||||
]
|
||||
};
|
||||
|
||||
/**
|
||||
* converts an WADO instance to a javascript object with the help of a data dictionary
|
||||
*
|
||||
* @param instance
|
||||
* @param options
|
||||
*/
|
||||
instanceDataToJsObject = function(instance, dictionary) {
|
||||
if (!instance) {
|
||||
throw 'instanceDataToJsObject: missing required parameter dataSet';
|
||||
}
|
||||
|
||||
var result = {};
|
||||
|
||||
// The comma is always inserted after the first four digits
|
||||
var position = 4;
|
||||
|
||||
Object.keys(instance).forEach(function(tag) {
|
||||
var item = instance[tag];
|
||||
|
||||
// Reformat the tag from 00020010 to "(0002,0010)" to suit the dictionary
|
||||
var tagFormatted = '(' + tag.substr(0, position) + ',' + tag.substr(position) + ')';
|
||||
|
||||
// Retrieve the tag dictionary entry
|
||||
var tagDictionaryEntry = dictionary[tagFormatted];
|
||||
console.log(tagDictionaryEntry);
|
||||
|
||||
// If there is no tag dictionary entry, or no name
|
||||
// for this tag, just use the original tag instead
|
||||
var tagName;
|
||||
if (!tagDictionaryEntry || !tagDictionaryEntry.name) {
|
||||
tagName = tag;
|
||||
} else {
|
||||
tagName = tagDictionaryEntry.name;
|
||||
}
|
||||
|
||||
if (item.Value && item.Value.length > 1) {
|
||||
// handle sequences
|
||||
var sequenceItems = [];
|
||||
item.Value.forEach(function(instance) {
|
||||
sequenceItems.push(instanceDataToJsObject(instance, dictionary));
|
||||
});
|
||||
result[tagName] = sequenceItems;
|
||||
|
||||
} else if (!item.Value || !item.Value.length) {
|
||||
// Handle empty arrays
|
||||
result[tagName] = undefined;
|
||||
|
||||
} else {
|
||||
// Handle single valued cases
|
||||
if (item.vr === 'PN') {
|
||||
// Patient Name
|
||||
result[tagName] = DICOMWeb.getName(item);
|
||||
|
||||
} else if (valueRepresentationTypes.number.indexOf(item.vr) > 0) {
|
||||
// Number type
|
||||
result[tagName] = DICOMWeb.getNumber(item);
|
||||
|
||||
} else {
|
||||
// Everything else
|
||||
result[tagName] = DICOMWeb.getString(item);
|
||||
}
|
||||
}
|
||||
});
|
||||
console.log(result);
|
||||
return result;
|
||||
};
|
||||
@@ -1,87 +0,0 @@
|
||||
var tags = {
|
||||
sopClassUid: '00080016'
|
||||
};
|
||||
|
||||
var tagValues = {
|
||||
HangingProtocolStorage: '1.2.840.10008.5.1.4.38.1'
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds appropriate hanging protocols given a studyInstanceUid
|
||||
*
|
||||
* This function retrieves all hanging protocols in storage
|
||||
* in order to find those that match the parameters of the given study
|
||||
*
|
||||
* @param studyInstanceUID
|
||||
* @returns {Array} An array of matching hanging protocols
|
||||
*/
|
||||
DICOMHP.match = function(studyInstanceUID) {
|
||||
// First, retrieve the metaData from the given study
|
||||
var studyInstance = Services.WADO.retrieveMetadata(studyInstanceUID);
|
||||
if (!studyInstance) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Note: It turns out Orthanc doesn't support hanging protocol storage yet,
|
||||
// so I will just stop here.
|
||||
var studyData = instanceDataToJsObject(studyInstance, TAG_DICT);
|
||||
|
||||
/*var searchParameters = {
|
||||
tags['sopClassUid']: tagValues['HangingProtocolStorage']
|
||||
};
|
||||
|
||||
|
||||
var modalities = studyData.ModalitiesInStudy;
|
||||
|
||||
|
||||
var modalityMatch;
|
||||
|
||||
if (modalities) {
|
||||
modalityMatch = [];
|
||||
|
||||
modalities.split('\\').forEach(function(modality){
|
||||
if (modality && modality != 'SC') {
|
||||
modalityMatch.push(modality);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
var firstInstance = Services.WADO..retrieveMetadata(studyInstanceUID, null, null, {limit : 1}),
|
||||
lateralityMatch;
|
||||
|
||||
if (firstInstance.length > 0) {
|
||||
lateralityMatch = firstInstance[0]["00200060"].Value[0];
|
||||
}
|
||||
|
||||
// since orthanc doesn't support sequence matching
|
||||
var allProtocols = Services.WADO..searchForInstances(null, null, params);
|
||||
|
||||
if (allProtocols.length < 1) {
|
||||
return [];
|
||||
}
|
||||
|
||||
var matchedProtocols = [];
|
||||
|
||||
allProtocols.forEach(function(protocol) {
|
||||
|
||||
var definitionSequence = protocol["0072000C"].Value;
|
||||
|
||||
definitionSequence.forEach(function(definition){
|
||||
|
||||
var defModality = definition["00080060"].Value[0];
|
||||
|
||||
if (defModality && modalityMatch && modalityMatch.indexOf(defModality) != -1) {
|
||||
matchedProtocols.push(protocol);
|
||||
return;
|
||||
}
|
||||
|
||||
var defLaterality = definition["00200060"].Value[0];
|
||||
if (defLaterality && (defLaterality == lateralityMatch)) {
|
||||
matchedProtocols.push(protocol);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return matchedProtocols;*/
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
/*DICOMTags = new Meteor.Collection(null);
|
||||
|
||||
Object.keys(HP.tagDescriptions).forEach(function(key) {
|
||||
var value = HP.tagDescriptions[key];
|
||||
DICOMTags.insert({
|
||||
id: key,
|
||||
name: '(' + key + ') ' + value
|
||||
});
|
||||
});
|
||||
|
||||
Meteor.methods({
|
||||
dicomTagSearch: function(partialName) {
|
||||
check(partialName, String);
|
||||
|
||||
var results = DICOMTags.find({
|
||||
name: {
|
||||
$regex: partialName,
|
||||
$options: 'i'
|
||||
}
|
||||
}, {
|
||||
limit: 20,
|
||||
fields: {
|
||||
id: 1,
|
||||
name: 1
|
||||
}
|
||||
}).fetch();
|
||||
|
||||
return {
|
||||
results: results
|
||||
};
|
||||
}
|
||||
}); */
|
||||
|
||||
Meteor.methods({
|
||||
removeHangingProtocol: function(id) {
|
||||
HangingProtocols.remove(id);
|
||||
},
|
||||
removeHangingProtocolByID: function(id) {
|
||||
HangingProtocols.remove({
|
||||
id: id
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1 +0,0 @@
|
||||
DICOMHP = {};
|
||||
@@ -1,78 +0,0 @@
|
||||
DICOMHP.select = function(hpInstance) {
|
||||
var imageSetsSequence = hpInstance['00720020'].Value;
|
||||
if (!imageSetsSequence) {
|
||||
return [];
|
||||
}
|
||||
|
||||
var matchedImageSets = [];
|
||||
imageSetsSequence.forEach(function(imageSet) {
|
||||
var selectorSequence = imageSet['00720022'].Value;
|
||||
selectorSequence.forEach(function(selector) {
|
||||
var usageFlag = selector['00720024'].Value[0],
|
||||
selectorAttribute = selector['00720026'].Value[0],
|
||||
selectorAttributeVR = selector['00720050'].Value[0],
|
||||
selectorAttributeValue = null;
|
||||
|
||||
if (selectorAttributeVR == 'SQ') {
|
||||
return;
|
||||
} else if (selectorAttributeVR == 'AT') {
|
||||
selectorAttributeValue = selector['00720060'].Value[0];
|
||||
} else if (selectorAttributeVR == 'CS') {
|
||||
selectorAttributeValue = selector['00720062'].Value[0];
|
||||
} else if (selectorAttributeVR == 'IS') {
|
||||
selectorAttributeValue = selector['00720064'].Value[0];
|
||||
} else if (selectorAttributeVR == 'LO') {
|
||||
selectorAttributeValue = selector['00720066'].Value[0];
|
||||
} else if (selectorAttributeVR == 'LT') {
|
||||
selectorAttributeValue = selector['00720068'].Value[0];
|
||||
} else if (selectorAttributeVR == 'PN') {
|
||||
selectorAttributeValue = selector['0072006A'].Value[0];
|
||||
} else if (selectorAttributeVR == 'SH') {
|
||||
selectorAttributeValue = selector['0072006C'].Value[0];
|
||||
} else if (selectorAttributeVR == 'ST') {
|
||||
selectorAttributeValue = selector['0072006E'].Value[0];
|
||||
} else if (selectorAttributeVR == 'UT') {
|
||||
selectorAttributeValue = selector['00720070'].Value[0];
|
||||
} else if (selectorAttributeVR == 'DS') {
|
||||
selectorAttributeValue = selector['00720072'].Value[0];
|
||||
} else if (selectorAttributeVR == 'FD') {
|
||||
selectorAttributeValue = selector['00720074'].Value[0];
|
||||
} else if (selectorAttributeVR == 'FL') {
|
||||
selectorAttributeValue = selector['00720076'].Value[0];
|
||||
} else if (selectorAttributeVR == 'UL') {
|
||||
selectorAttributeValue = selector['00720078'].Value[0];
|
||||
} else if (selectorAttributeVR == 'US') {
|
||||
selectorAttributeValue = selector['0072007A'].Value[0];
|
||||
} else if (selectorAttributeVR == 'SL') {
|
||||
selectorAttributeValue = selector['0072007C'].Value[0];
|
||||
} else if (selectorAttributeVR == 'SS') {
|
||||
selectorAttributeValue = selector['0072007E'].Value[0];
|
||||
}
|
||||
|
||||
if (selectorAttributeValue !== null) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
var timeBasedImageSetsSequence = imageSet['00720030'].Value;
|
||||
timeBasedImageSetsSequence.forEach(function(timeImageSet) {
|
||||
var setNumber = timeImageSet['00720032'].Value[0], selectorCategory = timeImageSet['00720034'].Value[0];
|
||||
|
||||
var mImageSet = new DICOMHP.imageSet(setNumber, selectorCategory);
|
||||
if (selectorCategory == 'RELATIVE_TIME') {
|
||||
var relativeTime = timeImageSet['00720038'].Value[0], timeUnits = timeImageSet['0072003A'].Value[0];
|
||||
|
||||
mImageSet.setRelativeTime(relativeTime);
|
||||
mImageSet.setTimeUnits(timeUnits);
|
||||
} else if (selectorCategory == 'ABSTRACT_PRIOR') {
|
||||
var priorValue = timeImageSet['0072003C'].Value[0];
|
||||
|
||||
mImageSet.setPriorValue(priorValue);
|
||||
}
|
||||
|
||||
matchedImageSets.push(mImageSet);
|
||||
});
|
||||
});
|
||||
|
||||
return matchedImageSets;
|
||||
};
|
||||
Reference in new issue
Block a user