Added a basic implementation of hanging protocols with one hardcoded mammogram protocol.
This commit is contained in:
parent
dac91857f0
commit
292edc11d3
@ -34,6 +34,7 @@ es5-shim@4.1.14
|
||||
fastclick@1.0.7
|
||||
fortawesome:fontawesome@4.4.0
|
||||
geojson-utils@1.0.4
|
||||
hangingprotocols@0.0.1
|
||||
hot-code-push@1.0.0
|
||||
html-tools@1.0.5
|
||||
htmljs@1.0.5
|
||||
|
||||
@ -34,3 +34,4 @@ viewerbase
|
||||
worklist
|
||||
reactive-var
|
||||
practicalmeteor:loglevel
|
||||
hangingprotocols
|
||||
|
||||
@ -32,6 +32,7 @@ email@1.0.8
|
||||
fastclick@1.0.7
|
||||
fortawesome:fontawesome@4.4.0
|
||||
geojson-utils@1.0.4
|
||||
hangingprotocols@0.0.1
|
||||
hot-code-push@1.0.0
|
||||
html-tools@1.0.5
|
||||
htmljs@1.0.5
|
||||
|
||||
@ -1,70 +0,0 @@
|
||||
var hangingProtocol;
|
||||
|
||||
/**
|
||||
* This is an example of a hanging protocol
|
||||
* It takes in a set of studies, as well as the
|
||||
* number of rows and columns in the layout.
|
||||
*
|
||||
* It returns an array of objects, one for each viewport, detailing
|
||||
* which series should be loaded in the viewport.
|
||||
*
|
||||
* @param inputData
|
||||
* @returns {Array}
|
||||
*/
|
||||
function defaultHangingProtocol(inputData) {
|
||||
// TODO = Update this to use Collection logic
|
||||
var studies = inputData.studies.find().fetch();
|
||||
var viewportRows = inputData.viewportRows;
|
||||
var viewportColumns = inputData.viewportColumns;
|
||||
|
||||
if (!studies.length) {
|
||||
log.warn("No studies provided to Hanging Protocol");
|
||||
return;
|
||||
}
|
||||
|
||||
// This is the most basic hanging protocol.
|
||||
var stacks = [];
|
||||
studies.forEach(function(study) {
|
||||
study.seriesList.forEach(function(series) {
|
||||
|
||||
// Ensure that the series has image data
|
||||
// (All images have rows)
|
||||
var anInstance = series.instances[0];
|
||||
if (!anInstance || !anInstance.rows) {
|
||||
return;
|
||||
}
|
||||
|
||||
var stack = {
|
||||
series: series,
|
||||
study: study
|
||||
};
|
||||
stacks.push(stack);
|
||||
});
|
||||
});
|
||||
|
||||
var viewportData = [];
|
||||
|
||||
var numViewports = viewportRows * viewportColumns;
|
||||
for (var i=0; i < numViewports; ++i) {
|
||||
if (i >= stacks.length) {
|
||||
// We don't have enough stacks to fill the desired number of viewports, so stop here
|
||||
break;
|
||||
}
|
||||
viewportData[i] = {
|
||||
seriesInstanceUid: stacks[i].series.seriesInstanceUid,
|
||||
studyInstanceUid: stacks[i].study.studyInstanceUid,
|
||||
currentImageIdIndex: 0
|
||||
};
|
||||
}
|
||||
return viewportData;
|
||||
}
|
||||
|
||||
getHangingProtocol = function() {
|
||||
return hangingProtocol;
|
||||
};
|
||||
|
||||
setHangingProtocol = function(protocol) {
|
||||
hangingProtocol = protocol;
|
||||
};
|
||||
|
||||
setHangingProtocol(defaultHangingProtocol);
|
||||
@ -17,7 +17,6 @@ Package.onUse(function (api) {
|
||||
api.addFiles('client/dicomParser.js', 'client', {bare: true});
|
||||
api.addFiles('client/hammer.js', 'client', {bare: true});
|
||||
|
||||
api.addFiles('client/hangingProtocol.js', 'client', {bare: true});
|
||||
api.addFiles('client/measurementManager.js', 'client', {bare: true});
|
||||
api.addFiles('client/measurementManagerExample.js', 'client', {bare: true});
|
||||
|
||||
@ -26,9 +25,6 @@ Package.onUse(function (api) {
|
||||
api.export("cornerstoneTools", 'client');
|
||||
api.export("cornerstoneWADOImageLoader", 'client');
|
||||
api.export("cornerstoneWADORSImageLoader", 'client');
|
||||
api.export("dicomParser", 'client');
|
||||
|
||||
api.export("getHangingProtocol", 'client');
|
||||
api.export("setHangingProtocol", 'client');
|
||||
api.export("dicomParser", ['client', 'server']);
|
||||
});
|
||||
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
Package.describe({
|
||||
name: "weiwei:hangingprotocols",
|
||||
summary: "DICOM Haning Protocols",
|
||||
version: '0.0.1'
|
||||
name: "hangingprotocols",
|
||||
summary: "Support functions for using DICOM Hanging Protocols",
|
||||
version: '0.0.1'
|
||||
});
|
||||
|
||||
Package.onUse(function (api) {
|
||||
api.use("weiwei:dicomservices");
|
||||
api.use('cornerstone');;
|
||||
|
||||
api.addFiles('server/namespace.js', 'server');
|
||||
api.addFiles('server/namespace.js', 'server');
|
||||
api.addFiles('server/dataDictionary.js', 'server');
|
||||
api.addFiles('server/instanceDataToJsObject.js', 'server');
|
||||
|
||||
api.export("DICOMHP", 'server');
|
||||
});
|
||||
|
||||
Package.onTest(function (api){
|
||||
api.use(["weiwei:hangingprotocols"]);
|
||||
api.export('instanceDataToJsObject', 'server');
|
||||
api.export('TAG_DICT', 'server');
|
||||
api.export("DICOMHP", ['client', 'server']);
|
||||
});
|
||||
3653
Packages/hangingprotocols/server/dataDictionary.js
Normal file
3653
Packages/hangingprotocols/server/dataDictionary.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,78 @@
|
||||
/**
|
||||
* 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;
|
||||
};
|
||||
73
Packages/hangingprotocols/server/instanceDataToJsObject.js
Normal file
73
Packages/hangingprotocols/server/instanceDataToJsObject.js
Normal file
@ -0,0 +1,73 @@
|
||||
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,46 +1,87 @@
|
||||
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) {
|
||||
var metadata = DICOMService.retrieveMetadata(studyInstanceUID);
|
||||
if (!metadata) {
|
||||
return [];
|
||||
}
|
||||
var params = {"00080016" : "1.2.840.10008.5.1.4.38.1"}, modalities = metadata["00080061"].Value[0],
|
||||
modalityMatch = null;
|
||||
|
||||
if (modalities) {
|
||||
modalityMatch = [];
|
||||
modalities.split('\\').forEach(function(modality){
|
||||
if (modality && modality != 'SC') {
|
||||
modalityMatch.push(modality);
|
||||
}
|
||||
});
|
||||
}
|
||||
var firstInstance = DICOMService.retrieveMetadata(studyInstanceUID, null, null, {limit : 1}),
|
||||
lateralityMatch = null;
|
||||
|
||||
if (firstInstance.length > 0) {
|
||||
lateralityMatch = firstInstance[0]["00200060"].Value[0];
|
||||
}
|
||||
|
||||
// since orthanc doesn't support sequence matching
|
||||
var allProtocols = DICOMService.searchForInstances(null, null, params);
|
||||
if (allProtocols.length < 1) {
|
||||
return [];
|
||||
}
|
||||
var matchedPrtocols = [];
|
||||
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) {
|
||||
matchedPrtocols.push(protocol);
|
||||
// First, retrieve the metaData from the given study
|
||||
var studyInstance = Services.WADO.retrieveMetadata(studyInstanceUID);
|
||||
if (!studyInstance) {
|
||||
return;
|
||||
}
|
||||
var defLaterality = definition["00200060"].Value[0];
|
||||
if (defLaterality && (defLaterality == lateralityMatch)) {
|
||||
matchedPrtocols.push(protocol);
|
||||
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 matchedPrtocols;
|
||||
|
||||
return matchedProtocols;*/
|
||||
};
|
||||
BIN
Packages/hangingprotocols/testData/GEMS_PARM_01.dcm
Normal file
BIN
Packages/hangingprotocols/testData/GEMS_PARM_01.dcm
Normal file
Binary file not shown.
11
Packages/hangingprotocols/testData/GEMS_PARM_01.xml
Normal file
11
Packages/hangingprotocols/testData/GEMS_PARM_01.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<dicom>
|
||||
<attr tag="00080008" vr="CS" len="22">ORIGINAL\PRIMARY\AXIAL</attr>
|
||||
<attr tag="00430010" vr="LO" len="12">GEMS_PARM_01</attr>
|
||||
<attr tag="00431012" vr="SS" len="6">14\2\3</attr>
|
||||
<attr tag="00431018" vr="DS" len="26">0.085000\1.102000\0.095000</attr>
|
||||
<attr tag="0043101A" vr="SL" len="4">7</attr>
|
||||
<attr tag="00431026" vr="US" len="12">0\1\1\0\0\0</attr>
|
||||
<attr tag="00431027" vr="SH" len="6">/1.0:1</attr>
|
||||
<attr tag="00431040" vr="FL" len="4">178.07993</attr>
|
||||
</dicom>
|
||||
BIN
Packages/hangingprotocols/testData/NeurosurgeryPlan.dcm
Normal file
BIN
Packages/hangingprotocols/testData/NeurosurgeryPlan.dcm
Normal file
Binary file not shown.
1487
Packages/hangingprotocols/testData/NeurosurgeryPlan.xml
Normal file
1487
Packages/hangingprotocols/testData/NeurosurgeryPlan.xml
Normal file
File diff suppressed because it is too large
Load Diff
@ -43,13 +43,13 @@ Template.imageViewerViewports.helpers({
|
||||
viewportData = ViewerData[contentId].loadedSeriesData;
|
||||
}
|
||||
|
||||
var hangingProtocol = getHangingProtocol();
|
||||
var inputData = {
|
||||
viewportColumns: viewportColumns,
|
||||
viewportRows: viewportRows,
|
||||
studies: ViewerStudies
|
||||
};
|
||||
var hangingProtocolViewportData = hangingProtocol(inputData);
|
||||
|
||||
var hangingProtocolViewportData = WindowManager.getHangingProtocol(inputData);
|
||||
|
||||
var array = [];
|
||||
var numViewports = viewportRows * viewportColumns;
|
||||
@ -75,7 +75,7 @@ Template.imageViewerViewports.helpers({
|
||||
array.push(data);
|
||||
}
|
||||
return array;
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
var savedSeriesData,
|
||||
@ -134,7 +134,7 @@ Template.imageViewerViewports.events({
|
||||
// Set the basic template data
|
||||
data = {
|
||||
viewportRows: 1,
|
||||
viewportColumns: 1,
|
||||
viewportColumns: 1
|
||||
};
|
||||
|
||||
// Render the imageViewerViewports template with these settings
|
||||
@ -145,6 +145,5 @@ Template.imageViewerViewports.events({
|
||||
$('.imageViewerViewport').eq(0).addClass('zoomed');
|
||||
|
||||
}
|
||||
log.info('dblclick');
|
||||
}
|
||||
});
|
||||
296
Packages/viewerbase/lib/windowManager.js
Normal file
296
Packages/viewerbase/lib/windowManager.js
Normal file
@ -0,0 +1,296 @@
|
||||
/**
|
||||
* This is a temporary function which will return a hardcoded hanging protocol as a JavaScript object
|
||||
* The purpose of this is to act as a stub until we are actually parsing DICOM Hanging Protocol files,
|
||||
* since Orthanc doesn't seem to support them yet.
|
||||
*
|
||||
* Soon we will be using instanceDataToJsObject to produce this object from the HangingProtocol WADO
|
||||
* instance
|
||||
*
|
||||
*/
|
||||
function getMammoHangingProtocol() {
|
||||
var tagValues = {
|
||||
HangingProtocolStorage: '1.2.840.10008.5.1.4.38.1'
|
||||
};
|
||||
|
||||
var protocol = {
|
||||
sopClassUid: tagValues.HangingProtocolStorage,
|
||||
sopInstanceUid: '1.2.840.113986.2.664566.21121125.85669.911', // A random Uid
|
||||
hangingProtocolName: 'MammoCadProtocol',
|
||||
hangingProtocolDescription: 'Mammography screening protocol',
|
||||
hangingProtocolLevel: 'SITE',
|
||||
hangingProtocolCreator: 'Erik',
|
||||
hangingProtocolCreationDateTime: '20020101104200',
|
||||
hangingProtocolDefinitionSequence: [{
|
||||
modality: 'MG',
|
||||
laterality: '',
|
||||
procedureCodeSequence: {
|
||||
codeValue: 98765,
|
||||
codingSchemeDesignator: '99Local',
|
||||
codingSchemeVersion: 1.5,
|
||||
codeMeaning: 'Mammogram'
|
||||
},
|
||||
anatomicRegionSequence: {
|
||||
codeValue: 'T-D1100',
|
||||
codingSchemeDesignator: 'SNM3',
|
||||
codeMeaning: 'BREAST'
|
||||
},
|
||||
reasonForRequestedProcedureCodeSequence: {
|
||||
codeValue: 'I67.1',
|
||||
codingSchemeDesignator: 'I10',
|
||||
codeMeaning: 'Calcification'
|
||||
}
|
||||
}],
|
||||
hangingProtocolUserIdentificationCodeSequence: [],
|
||||
hangingProtocolUserGroupName: 'ABC Hospital',
|
||||
numberOfPriorsReferenced: 1,
|
||||
imageSetsSequence: [{
|
||||
imageSetSelectorSequence: [{
|
||||
ImageSetSelectorUsageFlag: 'NO_MATCH',
|
||||
SelectorAttribute: '00180015',
|
||||
SelectorValueNumber: '1',
|
||||
SelectorAttributeVR: 'CS',
|
||||
SelectorCSValue: 'BREAST'
|
||||
}],
|
||||
timeBasedImageSetsSequence: [{
|
||||
ImageSetNumber: 1,
|
||||
ImageSetSelectorCategory: 'RELATIVE_TIME',
|
||||
RelativeTime: '0\0',
|
||||
RelativeTimeUnits: 'MINUTES',
|
||||
ImageSetLabel: 'Current MG Breast'
|
||||
},
|
||||
{
|
||||
ImageSetNumber: 2,
|
||||
ImageSetSelectorCategory: 'ABSTRACT_PRIOR',
|
||||
AbstractPriorValue: '1\1',
|
||||
ImageSetLabel: 'Prior MG Breast'
|
||||
}]
|
||||
}],
|
||||
// Skip Number of Screens for now,
|
||||
// Skip Nominal Screen Definition Sequence for now,
|
||||
|
||||
// http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.23.3.html
|
||||
DisplaySetsSequence: [{
|
||||
// Left side image (R MLO Current)
|
||||
ImageSetNumber: 1,
|
||||
DisplaySetNumber: 1,
|
||||
DisplaySetPresentationGroup: 1,
|
||||
DisplaySetPresentationGroupDescription: 'Current Mediolateral only',
|
||||
ImageBoxesSequence: [{
|
||||
DisplayEnvironmentSpatialPosition: '0\0.2\0.16667\0',
|
||||
ImageBoxNumber: 1,
|
||||
ImageBoxLayoutType: 'STACK'
|
||||
}],
|
||||
FilterOperationsSequence: [{
|
||||
SelectorAttributeVR: 'CS',
|
||||
SelectorCSValue: 'R MLO',
|
||||
FilterByCategory: 'SERIES_DESCRIPTION',
|
||||
FilterByOperator: 'MEMBER_OF'
|
||||
}],
|
||||
ReformattingOperationType: '',
|
||||
ReformattingThickness: '',
|
||||
ReformattingInterval: '',
|
||||
ReformattingOperationInitialViewDirection: '',
|
||||
SortingOperationsSequence: [],
|
||||
DisplaySetPatientOrientation: '',
|
||||
VOIType: ''
|
||||
}, {
|
||||
// Right side image (L MLO Current)
|
||||
ImageSetNumber: 1,
|
||||
DisplaySetNumber: 1,
|
||||
DisplaySetPresentationGroup: 1,
|
||||
ImageBoxesSequence: [{
|
||||
ImageBoxNumber: 1,
|
||||
ImageBoxLayoutType: 'STACK'
|
||||
}],
|
||||
FilterOperationsSequence: [{
|
||||
SelectorAttributeVR: 'CS',
|
||||
SelectorCSValue: 'L MLO',
|
||||
FilterByCategory: 'SERIES_DESCRIPTION',
|
||||
FilterByOperator: 'MEMBER_OF'
|
||||
}]
|
||||
}],
|
||||
PartialDataDisplayHandling: 'MAINTAIN_LAYOUT',
|
||||
SynchronizedScrollingSequence: [],
|
||||
NavigationIndicatorSequence: []
|
||||
};
|
||||
|
||||
return protocol;
|
||||
}
|
||||
|
||||
function findSetsByPresentationGroup(DisplaySetsSequence, DisplaySetPresentationGroup) {
|
||||
var presentationGroupSets = [];
|
||||
DisplaySetsSequence.forEach(function(displaySet) {
|
||||
if (displaySet.DisplaySetPresentationGroup === DisplaySetPresentationGroup) {
|
||||
presentationGroupSets.push(displaySet);
|
||||
}
|
||||
});
|
||||
return presentationGroupSets;
|
||||
}
|
||||
|
||||
function findStudy(displaySet, studies) {
|
||||
// Placeholder for now
|
||||
return studies.find().fetch()[0].studyInstanceUid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the FilterOperationsSequence information to find
|
||||
* the relevant series for display in this display set
|
||||
*
|
||||
* @param displaySet
|
||||
* @param study
|
||||
* @returns {String} The instance Uid of the series to be displayed
|
||||
*/
|
||||
function findSeries(displaySet, study) {
|
||||
var categoryDictionary = {
|
||||
SERIES_DESCRIPTION: 'seriesDescription'
|
||||
};
|
||||
|
||||
// TODO=Put these inside a loop? We need to support multiple filter operations,
|
||||
// not just one
|
||||
var filterBy = displaySet.FilterOperationsSequence[0].FilterByCategory;
|
||||
var selectorValue = displaySet.FilterOperationsSequence[0].SelectorCSValue;
|
||||
var filterType = displaySet.FilterOperationsSequence[0].FilterByOperator;
|
||||
var category = categoryDictionary[filterBy];
|
||||
|
||||
var seriesInstanceUid;
|
||||
study.seriesList.forEach(function(series) {
|
||||
// TODO = Add other FilterBy operators
|
||||
if (filterType === 'MEMBER_OF') {
|
||||
if (series[category] === selectorValue) {
|
||||
seriesInstanceUid = series.seriesInstanceUid;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return seriesInstanceUid;
|
||||
}
|
||||
|
||||
/**
|
||||
* (Work in progress) Uses the information from a DICOM Hanging Protocol
|
||||
* to identify and display studies and series in the image viewer
|
||||
*
|
||||
* @param hangingProtocol
|
||||
* @param inputData
|
||||
* @returns {Array} Array of viewport data to be displayed
|
||||
*/
|
||||
function applyDICOMHangingProtocol(hangingProtocol, inputData) {
|
||||
log.info('applyDICOMHangingProtocol');
|
||||
var presentationGroup = inputData.DisplaySetPresentationGroup || 1;
|
||||
var studies = inputData.studies;
|
||||
var currentDisplaySets = findSetsByPresentationGroup(hangingProtocol.DisplaySetsSequence, presentationGroup);
|
||||
|
||||
var viewportData = [];
|
||||
currentDisplaySets.forEach(function(displaySet, index) {
|
||||
// TODO= Find study information by image set properties and
|
||||
// image set number of this display set
|
||||
var ImageSetNumber = displaySet.ImageSetNumber;
|
||||
|
||||
studyInstanceUid = findStudy(displaySet, studies);
|
||||
|
||||
var study = studies.findOne({studyInstanceUid: studyInstanceUid});
|
||||
seriesInstanceUid = findSeries(displaySet, study);
|
||||
|
||||
viewportData[index] = {
|
||||
seriesInstanceUid: seriesInstanceUid,
|
||||
studyInstanceUid: studyInstanceUid,
|
||||
currentImageIdIndex: 0
|
||||
};
|
||||
});
|
||||
|
||||
return viewportData;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is an example of a hanging protocol
|
||||
* It takes in a set of studies, as well as the
|
||||
* number of rows and columns in the layout.
|
||||
*
|
||||
* It returns an array of objects, one for each viewport, detailing
|
||||
* which series should be loaded in the viewport.
|
||||
*
|
||||
* @param inputData
|
||||
* @returns {Array}
|
||||
*/
|
||||
function defaultHangingProtocol(inputData) {
|
||||
var studies = inputData.studies.find().fetch();
|
||||
var viewportRows = inputData.viewportRows;
|
||||
var viewportColumns = inputData.viewportColumns;
|
||||
|
||||
// This is the most basic hanging protocol.
|
||||
var stacks = [];
|
||||
studies.forEach(function(study) {
|
||||
study.seriesList.forEach(function(series) {
|
||||
|
||||
// Ensure that the series has image data
|
||||
// (All images have rows)
|
||||
var anInstance = series.instances[0];
|
||||
if (!anInstance || !anInstance.rows) {
|
||||
return;
|
||||
}
|
||||
|
||||
var stack = {
|
||||
series: series,
|
||||
study: study
|
||||
};
|
||||
stacks.push(stack);
|
||||
});
|
||||
});
|
||||
|
||||
var viewportData = [];
|
||||
|
||||
var numViewports = viewportRows * viewportColumns;
|
||||
for (var i=0; i < numViewports; ++i) {
|
||||
if (i >= stacks.length) {
|
||||
// We don't have enough stacks to fill the desired number of viewports, so stop here
|
||||
break;
|
||||
}
|
||||
viewportData[i] = {
|
||||
seriesInstanceUid: stacks[i].series.seriesInstanceUid,
|
||||
studyInstanceUid: stacks[i].study.studyInstanceUid,
|
||||
currentImageIdIndex: 0
|
||||
};
|
||||
}
|
||||
return viewportData;
|
||||
}
|
||||
|
||||
var currentStage = 0;
|
||||
var hangingProtocol;
|
||||
|
||||
function getHangingProtocol(inputData) {
|
||||
// TODO = Update this to use Collection logic
|
||||
var studies = inputData.studies.find().fetch();
|
||||
|
||||
if (!studies.length) {
|
||||
log.warn("No studies provided to Hanging Protocol");
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the unique modalities in this study
|
||||
var modalities = [];
|
||||
studies.forEach(function(study) {
|
||||
study.seriesList.forEach(function(series) {
|
||||
if (modalities.indexOf(series.modality) < 0) {
|
||||
modalities.push(series.modality);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (modalities.indexOf('MG') > -1) {
|
||||
var hangingProtocol = getMammoHangingProtocol();
|
||||
return applyDICOMHangingProtocol(hangingProtocol, inputData);
|
||||
}
|
||||
|
||||
return defaultHangingProtocol(inputData);
|
||||
}
|
||||
|
||||
function setHangingProtocol(protocol) {
|
||||
hangingProtocol = protocol;
|
||||
}
|
||||
|
||||
WindowManager = {
|
||||
setHangingProtocol: setHangingProtocol,
|
||||
getHangingProtocol: getHangingProtocol
|
||||
};
|
||||
|
||||
WindowManager.setHangingProtocol(defaultHangingProtocol);
|
||||
@ -18,6 +18,7 @@ Package.onUse(function (api) {
|
||||
|
||||
// Our custom package
|
||||
api.use('cornerstone');
|
||||
api.use('hangingprotocols');
|
||||
|
||||
api.addFiles('log.js', ['client', 'server']);
|
||||
|
||||
@ -102,6 +103,7 @@ Package.onUse(function (api) {
|
||||
api.addFiles('lib/rerenderViewportWithNewSeries.js', 'client');
|
||||
api.addFiles('lib/sortStudy.js', 'client');
|
||||
api.addFiles('lib/toolManager.js', 'client');
|
||||
api.addFiles('lib/windowManager.js', 'client');
|
||||
|
||||
//api.export('accountsConfig', 'client');
|
||||
api.export('createStacks', 'client');
|
||||
@ -112,9 +114,13 @@ Package.onUse(function (api) {
|
||||
api.export('metaDataProvider', 'client');
|
||||
api.export('rerenderViewportWithNewSeries', 'client');
|
||||
api.export('sortStudy', 'client');
|
||||
api.export('toolManager', 'client');
|
||||
api.export('updateOrientationMarkers', 'client');
|
||||
|
||||
// Viewer management objects
|
||||
api.export('toolManager', 'client');
|
||||
api.export('WindowManager', 'client');
|
||||
|
||||
|
||||
// UI Helpers
|
||||
api.addFiles('lib/helpers/formatDA.js', 'client');
|
||||
api.addFiles('lib/helpers/formatNumberPrecision.js', 'client');
|
||||
|
||||
Loading…
Reference in New Issue
Block a user