Updates for Timepoint assocation (LT-60) and Conformance Checks (LT-92, LT-93, LT-94)
This commit is contained in:
1 parent
6c4eae3699
commit
beaa56c0b3
144 files changed
+5050
-2416
No files matched your search
@@ -10,7 +10,7 @@ function getElementIfNotEmpty(viewportIndex) {
|
||||
element = imageViewerViewports.get(viewportIndex),
|
||||
canvases = imageViewerViewports.eq(viewportIndex).find('canvas');
|
||||
|
||||
if (!element || $(element).hasClass("empty") || canvases.length === 0) {
|
||||
if (!element || $(element).hasClass('empty') || canvases.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ function getElementIfNotEmpty(viewportIndex) {
|
||||
} catch(error) {
|
||||
return;
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
@@ -28,10 +29,12 @@ function getPatient(property) {
|
||||
if (!this.imageId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var patient = cornerstoneTools.metaData.get('patient', this.imageId);
|
||||
if (!patient) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return patient[property];
|
||||
}
|
||||
|
||||
@@ -40,10 +43,12 @@ function getStudy(property) {
|
||||
if (!this.imageId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var study = cornerstoneTools.metaData.get('study', this.imageId);
|
||||
if (!study) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return study[property];
|
||||
}
|
||||
|
||||
@@ -52,10 +57,12 @@ function getSeries(property) {
|
||||
if (!this.imageId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var series = cornerstoneTools.metaData.get('series', this.imageId);
|
||||
if (!series) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return series[property];
|
||||
}
|
||||
|
||||
@@ -64,10 +71,12 @@ function getInstance(property) {
|
||||
if (!this.imageId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var instance = cornerstoneTools.metaData.get('instance', this.imageId);
|
||||
if (!instance) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return instance[property];
|
||||
}
|
||||
|
||||
@@ -76,15 +85,18 @@ function getImage(viewportIndex) {
|
||||
if (!element) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var enabledElement;
|
||||
try {
|
||||
enabledElement = cornerstone.getEnabledElement(element);
|
||||
} catch(error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!enabledElement || !enabledElement.image) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return enabledElement.image;
|
||||
}
|
||||
|
||||
@@ -95,10 +107,12 @@ Template.viewportOverlay.helpers({
|
||||
if (!element) {
|
||||
return '';
|
||||
}
|
||||
|
||||
var viewport = cornerstone.getViewport(element);
|
||||
if (!viewport) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return 'W ' + viewport.voi.windowWidth.toFixed(0) + ' L ' + viewport.voi.windowCenter.toFixed(0);
|
||||
},
|
||||
zoom: function() {
|
||||
@@ -107,10 +121,12 @@ Template.viewportOverlay.helpers({
|
||||
if (!element) {
|
||||
return '';
|
||||
}
|
||||
|
||||
var viewport = cornerstone.getViewport(element);
|
||||
if (!viewport) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return (viewport.scale * 100.0);
|
||||
},
|
||||
imageDimensions: function() {
|
||||
@@ -118,41 +134,42 @@ Template.viewportOverlay.helpers({
|
||||
|
||||
var image = getImage(this.viewportIndex);
|
||||
if (!image) {
|
||||
return '';
|
||||
return '';
|
||||
}
|
||||
|
||||
return image.width + ' x ' + image.height;
|
||||
},
|
||||
patientName : function() {
|
||||
patientName: function() {
|
||||
return getPatient.call(this, 'name');
|
||||
},
|
||||
patientId : function() {
|
||||
patientId: function() {
|
||||
return getPatient.call(this, 'id');
|
||||
},
|
||||
studyDate : function() {
|
||||
studyDate: function() {
|
||||
return getStudy.call(this, 'studyDate');
|
||||
},
|
||||
studyTime : function() {
|
||||
studyTime: function() {
|
||||
return getStudy.call(this, 'studyTime');
|
||||
},
|
||||
studyDescription : function() {
|
||||
studyDescription: function() {
|
||||
return getStudy.call(this, 'studyDescription');
|
||||
},
|
||||
seriesDescription : function() {
|
||||
seriesDescription: function() {
|
||||
return getSeries.call(this, 'seriesDescription');
|
||||
},
|
||||
seriesNumber : function() {
|
||||
seriesNumber: function() {
|
||||
return getSeries.call(this, 'seriesNumber');
|
||||
},
|
||||
imageNumber : function() {
|
||||
imageNumber: function() {
|
||||
return getInstance.call(this, 'instanceNumber');
|
||||
},
|
||||
imageIndex : function() {
|
||||
imageIndex: function() {
|
||||
return getInstance.call(this, 'index');
|
||||
},
|
||||
numImages : function() {
|
||||
numImages: function() {
|
||||
return getSeries.call(this, 'numImages');
|
||||
},
|
||||
prior : function() {
|
||||
prior: function() {
|
||||
// This helper is updated whenever a new image is displayed in the viewport
|
||||
Session.get('CornerstoneNewImage' + this.viewportIndex);
|
||||
if (!this.imageId) {
|
||||
@@ -165,15 +182,19 @@ Template.viewportOverlay.helpers({
|
||||
// that we can obtain the oldest study as the first element of the array
|
||||
//
|
||||
// TODO= Find out if we should encode studyDate as a Date in the ViewerStudies Collection
|
||||
var viewportStudies = ViewerStudies.find({}, {sort: {studyDate: 1}});
|
||||
var viewportStudies = ViewerStudies.find({}, {
|
||||
sort: {
|
||||
studyDate: 1
|
||||
}
|
||||
});
|
||||
if (viewportStudies.count() < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get study data
|
||||
var study = cornerstoneTools.metaData.get('study', this.imageId);
|
||||
if (study.studyInstanceUid === viewportStudies.fetch()[0].studyInstanceUid) {
|
||||
if (study.studyDate === viewportStudies.fetch()[0].studyDate) {
|
||||
return 'Prior';
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
.imageViewerViewportOverlay
|
||||
font-family: Helvetica, OpenSans
|
||||
font-size: 13px
|
||||
color: #e4ad00
|
||||
color: #979797
|
||||
line-height: 18px
|
||||
display: none // Shown when an image is loaded
|
||||
|
||||
|
||||
@@ -7,40 +7,55 @@ Meteor.startup(function() {
|
||||
}
|
||||
|
||||
OHIF.viewer.defaultHotkeys = {
|
||||
defaultTool: "ESC",
|
||||
angle: "A",
|
||||
stackScroll: "S",
|
||||
pan: "P",
|
||||
magnify: "M",
|
||||
scrollDown: ["DOWN", "PAGEDOWN"],
|
||||
scrollUp: ["UP", "PAGEUP"],
|
||||
nextPanel: "RIGHT",
|
||||
previousPanel: "LEFT",
|
||||
invert: "I",
|
||||
flipV: "V",
|
||||
flipH: "H",
|
||||
wwwc: "W",
|
||||
zoom: "Z",
|
||||
cinePlay: "SPACE",
|
||||
rotateR: "R",
|
||||
rotateL: "L",
|
||||
defaultTool: 'ESC',
|
||||
angle: 'A',
|
||||
stackScroll: 'S',
|
||||
pan: 'P',
|
||||
magnify: 'M',
|
||||
scrollDown: ['DOWN', 'PAGEDOWN'],
|
||||
scrollUp: ['UP', 'PAGEUP'],
|
||||
nextPanel: 'RIGHT',
|
||||
previousPanel: 'LEFT',
|
||||
invert: 'I',
|
||||
flipV: 'V',
|
||||
flipH: 'H',
|
||||
wwwc: 'W',
|
||||
zoom: 'Z',
|
||||
cinePlay: 'SPACE',
|
||||
rotateR: 'R',
|
||||
rotateL: 'L',
|
||||
toggleOverlayTags: 'SHIFT',
|
||||
WLPresetSoftTissue: ["NUMPAD1", "1"],
|
||||
WLPresetLung: ["NUMPAD2", "2"],
|
||||
WLPresetLiver: ["NUMPAD3", "3"],
|
||||
WLPresetBone: ["NUMPAD4", "4"],
|
||||
WLPresetBrain: ["NUMPAD5", "5"]
|
||||
WLPresetSoftTissue: ['NUMPAD1', '1'],
|
||||
WLPresetLung: ['NUMPAD2', '2'],
|
||||
WLPresetLiver: ['NUMPAD3', '3'],
|
||||
WLPresetBone: ['NUMPAD4', '4'],
|
||||
WLPresetBrain: ['NUMPAD5', '5']
|
||||
};
|
||||
|
||||
// For now
|
||||
OHIF.viewer.hotkeys = OHIF.viewer.defaultHotkeys;
|
||||
|
||||
OHIF.viewer.defaultWLPresets = {
|
||||
'SoftTissue' : {wc : 40, ww : 400},
|
||||
'Lung' : {wc : -600, ww : 1500},
|
||||
'Liver' : {wc : 90, ww : 150},
|
||||
'Bone' : {wc: 480, ww : 2500},
|
||||
'Brain' : {wc : 40, ww : 80}
|
||||
SoftTissue: {
|
||||
wc: 40,
|
||||
ww: 400
|
||||
},
|
||||
Lung: {
|
||||
wc: -600,
|
||||
ww: 1500
|
||||
},
|
||||
Liver: {
|
||||
wc: 90,
|
||||
ww: 150
|
||||
},
|
||||
Bone: {
|
||||
wc: 480,
|
||||
ww: 2500
|
||||
},
|
||||
Brain: {
|
||||
wc: 40,
|
||||
ww: 80
|
||||
}
|
||||
};
|
||||
|
||||
// For now
|
||||
@@ -48,53 +63,53 @@ Meteor.startup(function() {
|
||||
|
||||
OHIF.viewer.hotkeyFunctions = {
|
||||
wwwc: function() {
|
||||
toolManager.setActiveTool("wwwc");
|
||||
toolManager.setActiveTool('wwwc');
|
||||
},
|
||||
zoom: function() {
|
||||
toolManager.setActiveTool("zoom");
|
||||
toolManager.setActiveTool('zoom');
|
||||
},
|
||||
angle: function() {
|
||||
toolManager.setActiveTool("angle");
|
||||
toolManager.setActiveTool('angle');
|
||||
},
|
||||
dragProbe: function() {
|
||||
toolManager.setActiveTool("dragProbe");
|
||||
toolManager.setActiveTool('dragProbe');
|
||||
},
|
||||
ellipticalRoi: function() {
|
||||
toolManager.setActiveTool("ellipticalRoi");
|
||||
toolManager.setActiveTool('ellipticalRoi');
|
||||
},
|
||||
magnify: function() {
|
||||
toolManager.setActiveTool("magnify");
|
||||
toolManager.setActiveTool('magnify');
|
||||
},
|
||||
annotate: function() {
|
||||
toolManager.setActiveTool("annotate");
|
||||
toolManager.setActiveTool('annotate');
|
||||
},
|
||||
stackScroll: function() {
|
||||
toolManager.setActiveTool("stackScroll");
|
||||
toolManager.setActiveTool('stackScroll');
|
||||
},
|
||||
pan: function() {
|
||||
toolManager.setActiveTool("pan");
|
||||
toolManager.setActiveTool('pan');
|
||||
},
|
||||
length: function() {
|
||||
toolManager.setActiveTool("length");
|
||||
toolManager.setActiveTool('length');
|
||||
},
|
||||
spine: function() {
|
||||
toolManager.setActiveTool("spine");
|
||||
toolManager.setActiveTool('spine');
|
||||
},
|
||||
wwwcRegion: function() {
|
||||
toolManager.setActiveTool("wwwcRegion");
|
||||
toolManager.setActiveTool('wwwcRegion');
|
||||
},
|
||||
zoomIn: function () {
|
||||
var button = document.getElementById("zoomIn");
|
||||
zoomIn: function() {
|
||||
var button = document.getElementById('zoomIn');
|
||||
flashButton(button);
|
||||
zoomIn();
|
||||
},
|
||||
zoomOut: function () {
|
||||
var button = document.getElementById("zoomOut");
|
||||
zoomOut: function() {
|
||||
var button = document.getElementById('zoomOut');
|
||||
flashButton(button);
|
||||
zoomOut();
|
||||
},
|
||||
zoomToFit: function () {
|
||||
var button = document.getElementById("zoomToFit");
|
||||
zoomToFit: function() {
|
||||
var button = document.getElementById('zoomToFit');
|
||||
flashButton(button);
|
||||
zoomToFit();
|
||||
},
|
||||
@@ -134,27 +149,27 @@ Meteor.startup(function() {
|
||||
previousActivePanel();
|
||||
},
|
||||
invert: function() {
|
||||
var button = document.getElementById("invert");
|
||||
var button = document.getElementById('invert');
|
||||
flashButton(button);
|
||||
invert();
|
||||
},
|
||||
flipV: function() {
|
||||
var button = document.getElementById("flipV");
|
||||
var button = document.getElementById('flipV');
|
||||
flashButton(button);
|
||||
flipV();
|
||||
},
|
||||
flipH: function() {
|
||||
var button = document.getElementById("flipH");
|
||||
var button = document.getElementById('flipH');
|
||||
flashButton(button);
|
||||
flipH();
|
||||
},
|
||||
rotateR: function() {
|
||||
var button = document.getElementById("rotateR");
|
||||
var button = document.getElementById('rotateR');
|
||||
flashButton(button);
|
||||
rotateR();
|
||||
},
|
||||
rotateL: function() {
|
||||
var button = document.getElementById("rotateL");
|
||||
var button = document.getElementById('rotateL');
|
||||
flashButton(button);
|
||||
rotateL();
|
||||
},
|
||||
@@ -174,6 +189,8 @@ Meteor.startup(function() {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
OHIF.viewer.loadedSeriesData = {};
|
||||
});
|
||||
|
||||
// Define a jQuery reverse function
|
||||
@@ -198,7 +215,6 @@ function previousActivePanel() {
|
||||
setActiveViewport(element);
|
||||
}
|
||||
|
||||
|
||||
function nextActivePanel() {
|
||||
log.info('nextActivePanel');
|
||||
var currentIndex = Session.get('activeViewport');
|
||||
@@ -224,7 +240,7 @@ function flashButton(button) {
|
||||
}
|
||||
|
||||
button.classList.add('active');
|
||||
setTimeout(function () {
|
||||
setTimeout(function() {
|
||||
button.classList.remove('active');
|
||||
}, 100);
|
||||
}
|
||||
@@ -233,13 +249,13 @@ function bindHotkey(hotkey, task) {
|
||||
var hotkeyFunctions = OHIF.viewer.hotkeyFunctions;
|
||||
|
||||
// Only bind defined, non-empty HotKeys
|
||||
if (!hotkey || hotkey === "") {
|
||||
if (!hotkey || hotkey === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
var fn;
|
||||
if (task.indexOf("WLPreset") > -1) {
|
||||
var presetName = task.replace("WLPreset", "");
|
||||
if (task.indexOf('WLPreset') > -1) {
|
||||
var presetName = task.replace('WLPreset', '');
|
||||
fn = function() {
|
||||
applyWLPresetToActiveElement(presetName);
|
||||
};
|
||||
@@ -282,4 +298,4 @@ enableHotkeys = function() {
|
||||
bindHotkey(taskHotkeys, task);
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
@@ -29,8 +29,14 @@ getWADORSImageId = function(instance) {
|
||||
//maxPixelValue : 255,
|
||||
slope: instance.rescaleSlope,
|
||||
intercept: instance.rescaleIntercept,
|
||||
windowCenter : windowCenter,
|
||||
windowWidth : windowWidth,
|
||||
samplesPerPixel: instance.samplesPerPixel,
|
||||
imageOrientationPatient: instance.imageOrientationPatient,
|
||||
imagePositionPatient: instance.imagePositionPatient,
|
||||
sopClassUid: instance.sopClassUid,
|
||||
instanceNumber: instance.instanceNumber,
|
||||
frameOfReferenceUID: instance.frameOfReferenceUID,
|
||||
windowCenter: windowCenter,
|
||||
windowWidth: windowWidth,
|
||||
//render: cornerstone.renderColorImage,
|
||||
//getPixelData: getPixelData,
|
||||
//getImageData: getImageData,
|
||||
@@ -47,6 +53,8 @@ getWADORSImageId = function(instance) {
|
||||
instance: instance
|
||||
};
|
||||
|
||||
var imageId = cornerstoneWADORSImageLoader.addImage(image);
|
||||
var imageId = cornerstoneWADOImageLoader.imageManager.add(image);
|
||||
|
||||
console.log('WADO-RS ImageID: ' + imageId);
|
||||
return imageId;
|
||||
};
|
||||
};
|
||||
@@ -1,9 +1,14 @@
|
||||
/**
|
||||
* Formats a patient name for display purposes
|
||||
*/
|
||||
formatPN = function(context) {
|
||||
if (!context) {
|
||||
return;
|
||||
}
|
||||
return context.replace('^', ', ');
|
||||
};
|
||||
|
||||
/**
|
||||
* A global Blaze UI helper to format a patient name for display purposes
|
||||
*/
|
||||
UI.registerHelper('formatPN', function (context) {
|
||||
if (!context) {
|
||||
return undefined;
|
||||
}
|
||||
return context.replace('^', ', ');
|
||||
});
|
||||
UI.registerHelper('formatPN', formatPN);
|
||||
@@ -7,4 +7,4 @@ UI.registerHelper("inlineIf", function (value, match, attributeName) {
|
||||
return attributeName;
|
||||
}
|
||||
return '';
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Helper function to determine if the current client devices
|
||||
* is touch-capable. This can be used to modify certain aspects of the UI.
|
||||
*
|
||||
* The check may not work on all devices!
|
||||
*
|
||||
* @returns {boolean} true if the client device is touch-capable, false otherwise
|
||||
*/
|
||||
isTouchDevice = function() {
|
||||
return (('ontouchstart' in window) ||
|
||||
(navigator.MaxTouchPoints > 0) ||
|
||||
(navigator.msMaxTouchPoints > 0));
|
||||
};
|
||||
|
||||
/**
|
||||
* Blaze helper for checking if the current device is touch capable
|
||||
*
|
||||
* @returns {boolean} true if the client device is touch-capable, false otherwise
|
||||
*/
|
||||
UI.registerHelper('isTouchDevice', isTouchDevice);
|
||||
@@ -1,13 +0,0 @@
|
||||
/**
|
||||
* Helper function to determine if the current client devices
|
||||
* is touch-capable. This can be used to modify certain aspects of the UI.
|
||||
*
|
||||
* The check may not work on all devices!
|
||||
*
|
||||
* @returns {boolean} true if the client device is touch-capable, false otherwise
|
||||
*/
|
||||
isTouchDevice = function() {
|
||||
return (('ontouchstart' in window) ||
|
||||
(navigator.MaxTouchPoints > 0) ||
|
||||
(navigator.msMaxTouchPoints > 0));
|
||||
};
|
||||
@@ -46,6 +46,7 @@ addMetaData = function(imageId, data) {
|
||||
instanceNumber: instanceMetaData.instanceNumber,
|
||||
laterality: instanceMetaData.laterality,
|
||||
viewPosition: instanceMetaData.viewPosition,
|
||||
sliceThickness: instanceMetaData.sliceThickness,
|
||||
index: imageIndex
|
||||
};
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -16,7 +16,9 @@ Package.onUse(function (api) {
|
||||
api.use('practicalmeteor:loglevel');
|
||||
api.use('momentjs:moment');
|
||||
|
||||
// Our custom package
|
||||
// Our custom packages
|
||||
api.use('dicomweb');
|
||||
api.use('dimseservice');
|
||||
api.use('cornerstone');
|
||||
api.use('hangingprotocols');
|
||||
|
||||
@@ -107,7 +109,6 @@ Package.onUse(function (api) {
|
||||
api.addFiles('lib/createStacks.js', 'client');
|
||||
api.addFiles('lib/getImageId.js', 'client');
|
||||
api.addFiles('lib/getWADORSImageId.js', 'client');
|
||||
api.addFiles('lib/isTouchDevice.js', 'client');
|
||||
api.addFiles('lib/metaDataProvider.js', 'client');
|
||||
api.addFiles('lib/rerenderViewportWithNewSeries.js', 'client');
|
||||
api.addFiles('lib/sortStudy.js', 'client');
|
||||
@@ -125,6 +126,7 @@ Package.onUse(function (api) {
|
||||
api.addFiles('lib/setFocusToActiveViewport.js', 'client');
|
||||
api.addFiles('lib/encodeQueryData.js', 'server');
|
||||
|
||||
|
||||
//api.export('accountsConfig', 'client');
|
||||
api.export('resizeViewportElements','client');
|
||||
api.export('handleResize','client');
|
||||
@@ -135,7 +137,6 @@ Package.onUse(function (api) {
|
||||
api.export('createStacks', 'client');
|
||||
api.export('getImageId', 'client');
|
||||
api.export('getWADORSImageId', 'client');
|
||||
api.export('isTouchDevice', 'client');
|
||||
api.export('metaDataProvider', 'client');
|
||||
api.export('rerenderViewportWithNewSeries', 'client');
|
||||
api.export('sortStudy', 'client');
|
||||
@@ -153,18 +154,31 @@ Package.onUse(function (api) {
|
||||
// UI Helpers
|
||||
api.addFiles('lib/helpers/formatDA.js', 'client');
|
||||
api.addFiles('lib/helpers/formatNumberPrecision.js', 'client');
|
||||
api.addFiles('lib/helpers/formatPN.js', 'client');
|
||||
api.addFiles('lib/helpers/formatTM.js', 'client');
|
||||
api.addFiles('lib/helpers/inlineIf.js', 'client');
|
||||
|
||||
api.addFiles('lib/helpers/formatPN.js', 'client');
|
||||
api.export('formatPN', 'client');
|
||||
|
||||
api.addFiles('lib/helpers/isTouchDevice.js', 'client');
|
||||
api.export('isTouchDevice', 'client');
|
||||
|
||||
// Server-side functions
|
||||
api.addFiles('server/seed.js', 'server');
|
||||
api.addFiles('server/lib/namespace.js', 'server');
|
||||
api.addFiles('server/methods/getStudyMetadata.js', 'server');
|
||||
api.addFiles('server/methods/worklistSearch.js', 'server');
|
||||
|
||||
// DICOMWeb instance, study, and metadata retrieval
|
||||
api.addFiles('server/services/qido/instances.js', 'server');
|
||||
api.addFiles('server/services/qido/studies.js', 'server');
|
||||
api.addFiles('server/services/wado/retrieveMetadata.js', 'server');
|
||||
|
||||
// DIMSE instance, study, and metadata retrieval
|
||||
api.addFiles('server/services/dimse/instances.js', 'server');
|
||||
api.addFiles('server/services/dimse/studies.js', 'server');
|
||||
api.addFiles('server/services/dimse/retrieveMetadata.js', 'server');
|
||||
|
||||
api.export('Services', 'server');
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
Services = {};
|
||||
Services.QIDO = {};
|
||||
Services.WADO = {};
|
||||
Services.WADO = {};
|
||||
Services.DIMSE = {};
|
||||
@@ -3,13 +3,17 @@ Meteor.methods({
|
||||
* Retrieves Study metadata given a Study Instance UID
|
||||
* This Meteor method is available from both the client and the server
|
||||
*/
|
||||
'GetStudyMetadata' : function(studyInstanceUid) {
|
||||
GetStudyMetadata: function(studyInstanceUid) {
|
||||
log.info('GetStudyMetadata(%s)', studyInstanceUid);
|
||||
|
||||
if (!Meteor.settings.dicomWeb) {
|
||||
throw 'No properly configured server was available over DICOMWeb';
|
||||
}
|
||||
|
||||
// Get the server data. This is user-defined in the
|
||||
// config.json files used to run the Meteor server
|
||||
var server = Meteor.settings.dicomWeb.endpoints[0];
|
||||
|
||||
return Services.WADO.RetrieveMetadata(server, studyInstanceUid);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -3,16 +3,17 @@ Meteor.methods({
|
||||
* Use the specified filter to conduct a search from the DICOM server
|
||||
* @param filter
|
||||
*/
|
||||
'WorklistSearch': function(filter) {
|
||||
if (!Meteor.settings.dicomWeb) {
|
||||
console.warn('No dicomWeb settings provided! Worklist search is not possible!')
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the server data. This is user-defined in the
|
||||
// config.json files used to run the Meteor server
|
||||
var server = Meteor.settings.dicomWeb.endpoints[0];
|
||||
WorklistSearch: function(filter) {
|
||||
if (Meteor.settings.dicomWeb && Meteor.settings.defaultServiceType === 'dicomWeb') {
|
||||
// Get the server data. This is user-defined in the
|
||||
// config.json files used to run the Meteor server
|
||||
var server = Meteor.settings.dicomWeb.endpoints[0];
|
||||
|
||||
return Services.QIDO.Studies(server, filter);
|
||||
return Services.QIDO.Studies(server, filter);
|
||||
} else if (Meteor.settings.dimse && Meteor.settings.defaultServiceType === 'dimse') {
|
||||
return Services.DIMSE.Studies(filter);
|
||||
} else {
|
||||
throw 'No properly configured server was available over DICOMWeb or DIMSE.';
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Parses data returned from a study search and transforms it into
|
||||
* an array of series that are present in the study
|
||||
*
|
||||
* @param resultData
|
||||
* @returns {Array} Series List
|
||||
*/
|
||||
function resultDataToStudyMetadata(resultData) {
|
||||
var seriesMap = {};
|
||||
var seriesList = [];
|
||||
|
||||
resultData.forEach(function(instanceRaw) {
|
||||
var instance = instanceRaw.toObject();
|
||||
// Use seriesMap to cache series data
|
||||
// If the series instance UID has already been used to
|
||||
// process series data, continue using that series
|
||||
var seriesInstanceUid = instance[0x0020000E];
|
||||
var series = seriesMap[seriesInstanceUid];
|
||||
|
||||
// If no series data exists in the seriesMap cache variable,
|
||||
// process any available series data
|
||||
if (!series) {
|
||||
series = {
|
||||
seriesInstanceUid: seriesInstanceUid,
|
||||
seriesNumber: instance[0x00200011],
|
||||
instances: []
|
||||
};
|
||||
|
||||
// Save this data in the seriesMap cache variable
|
||||
seriesMap[seriesInstanceUid] = series;
|
||||
seriesList.push(series);
|
||||
}
|
||||
|
||||
var host = Meteor.settings.dimse.host,
|
||||
port = Meteor.settings.dimse.port;
|
||||
|
||||
var serverRoot = host + ':' + port;
|
||||
|
||||
log.info('INSTANCE');
|
||||
log.info(instance);
|
||||
var sopInstanceUid = instance[0x00080018];
|
||||
var uri = serverRoot + '/studies/' + studyInstanceUid + '/series/' + seriesInstanceUid + '/instances/' + sopInstanceUid + '/frames/1';
|
||||
|
||||
// Add this instance to the current series
|
||||
series.instances.push({
|
||||
sopClassUid: instance[0x00080016],
|
||||
sopInstanceUid: sopInstanceUid,
|
||||
uri: uri,
|
||||
instanceNumber: instance[0x00200013]
|
||||
});
|
||||
});
|
||||
return seriesList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a set of instances using a DIMSE call
|
||||
* @param studyInstanceUid
|
||||
* @returns {{wadoUriRoot: String, studyInstanceUid: String, seriesList: Array}}
|
||||
*/
|
||||
Services.DIMSE.Instances = function(studyInstanceUid) {
|
||||
//var url = buildUrl(server, studyInstanceUid);
|
||||
var result = DIMSE.retrieveInstances(studyInstanceUid);
|
||||
|
||||
console.log("DIMSE Instance retrieval");
|
||||
console.log(result);
|
||||
return {
|
||||
studyInstanceUid: studyInstanceUid,
|
||||
seriesList: resultDataToStudyMetadata(result)
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* Parses the SourceImageSequence, if it exists, in order
|
||||
* to return a ReferenceSOPInstanceUID. The ReferenceSOPInstanceUID
|
||||
* is used to refer to this image in any accompanying DICOM-SR documents.
|
||||
*
|
||||
* @param instance
|
||||
* @returns {String} The ReferenceSOPInstanceUID
|
||||
*/
|
||||
function getSourceImageInstanceUid(instance) {
|
||||
// TODO= Parse the whole Source Image Sequence
|
||||
// This is a really poor workaround for now.
|
||||
// Later we should probably parse the whole sequence.
|
||||
var SourceImageSequence = instance[0x00082112];
|
||||
log.info('SourceImageSequence ' + SourceImageSequence);
|
||||
if (SourceImageSequence && SourceImageSequence.length) {
|
||||
return SourceImageSequence[0][0x00081155];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses result data from a DIMSE search into Study MetaData
|
||||
* Returns an object populated with study metadata, including the
|
||||
* series list.
|
||||
*
|
||||
* @param resultData
|
||||
* @returns {{seriesList: Array, patientName: *, patientId: *, accessionNumber: *, studyDate: *, modalities: *, studyDescription: *, imageCount: *, studyInstanceUid: *}}
|
||||
*/
|
||||
function resultDataToStudyMetadata(studyInstanceUid, resultData) {
|
||||
var seriesMap = {};
|
||||
var seriesList = [];
|
||||
|
||||
if (!resultData.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var anInstance = resultData[0].toObject();
|
||||
if (!anInstance) {
|
||||
return;
|
||||
}
|
||||
|
||||
var studyData = {
|
||||
seriesList: seriesList,
|
||||
patientName: anInstance[0x00100010],
|
||||
patientId: anInstance[0x00100020],
|
||||
accessionNumber: anInstance[0x00080050],
|
||||
studyDate: anInstance[0x00080020],
|
||||
modalities: anInstance[0x00080061],
|
||||
studyDescription: anInstance[0x00081030],
|
||||
imageCount: anInstance[0x00201208],
|
||||
studyInstanceUid: anInstance[0x0020000D]
|
||||
};
|
||||
|
||||
resultData.forEach(function(instanceRaw) {
|
||||
var instance = instanceRaw.toObject();
|
||||
var seriesInstanceUid = instance[0x0020000E];
|
||||
var series = seriesMap[seriesInstanceUid];
|
||||
if (!series) {
|
||||
series = {
|
||||
seriesDescription: instance[0x0008103E],
|
||||
modality: instance[0x00080060],
|
||||
seriesInstanceUid: seriesInstanceUid,
|
||||
seriesNumber: parseFloat(instance[0x00200011]),
|
||||
instances: []
|
||||
};
|
||||
seriesMap[seriesInstanceUid] = series;
|
||||
seriesList.push(series);
|
||||
}
|
||||
|
||||
var sopInstanceUid = instance[0x00080018];
|
||||
|
||||
log.info('instance');
|
||||
log.info(instance);
|
||||
var instanceSummary = {
|
||||
imageType: instance[0x00080008],
|
||||
sopClassUid: instance[0x00080016],
|
||||
sopInstanceUid: sopInstanceUid,
|
||||
instanceNumber: parseFloat(instance[0x00200013]),
|
||||
imagePositionPatient: instance[0x00200032],
|
||||
imageOrientationPatient: instance[0x00200037],
|
||||
frameOfReferenceUID: instance[0x00200052],
|
||||
sliceLocation: parseFloat(instance[0x00201041]),
|
||||
samplesPerPixel: parseFloat(instance[0x00280002]),
|
||||
photometricInterpretation: instance[0x00280004],
|
||||
rows: parseFloat(instance[0x00280010]),
|
||||
columns: parseFloat(instance[0x00280011]),
|
||||
pixelSpacing: instance[0x00280030],
|
||||
bitsAllocated: parseFloat(instance[0x00280100]),
|
||||
bitsStored: parseFloat(instance[0x00280101]),
|
||||
highBit: parseFloat(instance[0x00280102]),
|
||||
pixelRepresentation: parseFloat(instance[0x00280103]),
|
||||
windowCenter: instance[0x00281050],
|
||||
windowWidth: instance[0x00281051],
|
||||
rescaleIntercept: parseFloat(instance[0x00281052]),
|
||||
rescaleSlope: parseFloat(instance[0x00281053]),
|
||||
sourceImageInstanceUid: getSourceImageInstanceUid(instance),
|
||||
laterality: instance[0x00200062],
|
||||
viewPosition: instance[0x00185101],
|
||||
numFrames: parseFloat(instance[0x00280008]),
|
||||
frameRate: parseFloat(instance[0x00181063])
|
||||
};
|
||||
|
||||
var host = Meteor.settings.dimse.host,
|
||||
port = Meteor.settings.dimse.port;
|
||||
|
||||
var serverRoot = host + ':' + port;
|
||||
|
||||
instanceSummary.wadorsuri = serverRoot + '/studies/' + studyInstanceUid + '/series/' + seriesInstanceUid + '/instances/' + sopInstanceUid + '/frames/1';
|
||||
|
||||
series.instances.push(instanceSummary);
|
||||
});
|
||||
|
||||
return studyData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieved Study MetaData from a DICOM server using DIMSE
|
||||
* @param studyInstanceUid
|
||||
* @returns {{seriesList: Array, patientName: *, patientId: *, accessionNumber: *, studyDate: *, modalities: *, studyDescription: *, imageCount: *, studyInstanceUid: *}}
|
||||
*/
|
||||
Services.DIMSE.RetrieveMetadata = function(studyInstanceUid) {
|
||||
console.log('DIMSE RetrieveMetadata');
|
||||
|
||||
var result = DIMSE.retrieveInstances(studyInstanceUid, null, {
|
||||
0x00080050: '',
|
||||
0x00080061: '',
|
||||
0x00081030: '',
|
||||
0x00201208: '',
|
||||
0x00080060: '',
|
||||
0x00080008: '',
|
||||
0x00200032: '',
|
||||
0x00200037: '',
|
||||
0x00200052: '',
|
||||
0x00201041: '',
|
||||
0x00280002: '',
|
||||
0x00280004: '',
|
||||
0x00280030: '',
|
||||
0x00280100: '',
|
||||
0x00280101: '',
|
||||
0x00280102: '',
|
||||
0x00280103: '',
|
||||
0x00281050: '',
|
||||
0x00281051: '',
|
||||
0x00281052: '',
|
||||
0x00281053: '',
|
||||
0x00200062: '',
|
||||
0x00185101: '',
|
||||
0x00280008: '',
|
||||
0x00181063: '',
|
||||
0x00082112: ''
|
||||
});
|
||||
|
||||
var study = resultDataToStudyMetadata(studyInstanceUid, result);
|
||||
if (!study) {
|
||||
study = {};
|
||||
}
|
||||
|
||||
study.studyInstanceUid = studyInstanceUid;
|
||||
|
||||
console.log(study);
|
||||
console.log(study.seriesList[0].instances[0]);
|
||||
return study;
|
||||
};
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Parses resulting data from a QIDO call into a set of Study MetaData
|
||||
*
|
||||
* @param resultData
|
||||
* @returns {Array} An array of Study MetaData objects
|
||||
*/
|
||||
function resultDataToStudies(resultData) {
|
||||
var studies = [];
|
||||
|
||||
resultData.forEach(function(studyRaw) {
|
||||
var study = studyRaw.toObject();
|
||||
studies.push({
|
||||
studyInstanceUid: study[0x0020000D],
|
||||
// 00080005 = SpecificCharacterSet
|
||||
studyDate: study[0x00080020],
|
||||
studyTime: study[0x00080030],
|
||||
accessionNumber: study[0x00080050],
|
||||
referringPhysicianName: study[0x00080090],
|
||||
// 00081190 = URL
|
||||
patientName: study[0x00100010],
|
||||
patientId: study[0x00100020],
|
||||
patientBirthdate: study[0x00100030],
|
||||
patientSex: study[0x00100040],
|
||||
imageCount: study[0x00201208],
|
||||
studyId: study[0x00200010],
|
||||
studyDescription: study[0x00081030],
|
||||
modalities: study[0x00080061]
|
||||
});
|
||||
});
|
||||
return studies;
|
||||
}
|
||||
|
||||
|
||||
Services.DIMSE.Studies = function(filter) {
|
||||
console.log('Services.DIMSE.Studies');
|
||||
var parameters = {
|
||||
0x00100010: filter.patientName,
|
||||
0x00100020: filter.patientId,
|
||||
0x00080050: filter.accessionNumber,
|
||||
0x00081030: filter.studyDescription,
|
||||
0x00100040: "",
|
||||
0x00201208: "",
|
||||
0x00080061: ""
|
||||
};
|
||||
|
||||
var results = DIMSE.retrieveStudies(parameters);
|
||||
return resultDataToStudies(results);
|
||||
};
|
||||
@@ -30,10 +30,10 @@ function resultDataToStudyMetadata(server, studyInstanceUid, resultData) {
|
||||
|
||||
// If no series data exists in the seriesMap cache variable,
|
||||
// process any available series data
|
||||
if(!series) {
|
||||
if (!series) {
|
||||
series = {
|
||||
seriesInstanceUid : seriesInstanceUid,
|
||||
seriesNumber : DICOMWeb.getString(instance['00200011']),
|
||||
seriesInstanceUid: seriesInstanceUid,
|
||||
seriesNumber: DICOMWeb.getString(instance['00200011']),
|
||||
instances: []
|
||||
};
|
||||
|
||||
@@ -52,7 +52,7 @@ function resultDataToStudyMetadata(server, studyInstanceUid, resultData) {
|
||||
// manually create a WADO-URI from the UIDs
|
||||
// NOTE: Haven't been able to get Orthanc's WADO-URI to work yet - maybe its not configured?
|
||||
var sopInstanceUid = DICOMWeb.getString(instance['00080018']);
|
||||
var uri = server.wadoUriRoot + '?requestType=WADO&studyUID=' + studyInstanceUid + '&seriesUID=' + seriesInstanceUid + '&objectUID=' + sopInstanceUid + "&contentType=application%2Fdicom";
|
||||
var uri = server.wadoUriRoot + '?requestType=WADO&studyUID=' + studyInstanceUid + '&seriesUID=' + seriesInstanceUid + '&objectUID=' + sopInstanceUid + '&contentType=application%2Fdicom';
|
||||
|
||||
// Add this instance to the current series
|
||||
series.instances.push({
|
||||
@@ -61,8 +61,8 @@ function resultDataToStudyMetadata(server, studyInstanceUid, resultData) {
|
||||
uri: uri,
|
||||
instanceNumber: DICOMWeb.getString(instance['00200013'])
|
||||
});
|
||||
});
|
||||
return seriesList;
|
||||
});
|
||||
return seriesList;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,4 +80,4 @@ Services.QIDO.Instances = function(server, studyInstanceUid) {
|
||||
studyInstanceUid: studyInstanceUid,
|
||||
seriesList: resultDataToStudyMetadata(server, studyInstanceUid, result.data)
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -16,6 +16,7 @@ function filterToQIDOURL(server, filter) {
|
||||
PatientName: filter.patientName,
|
||||
PatientID: filter.patientId,
|
||||
AccessionNumber: filter.accessionNumber,
|
||||
StudyDescription: filter.studyDescription,
|
||||
limit: filter.limit || 20,
|
||||
includefield: server.qidoSupportsIncludeField ? 'all' : commaSeparatedFields
|
||||
};
|
||||
|
||||
@@ -105,7 +105,8 @@ function resultDataToStudyMetadata(server, studyInstanceUid, resultData) {
|
||||
laterality: DICOMWeb.getString(instance['00200062']),
|
||||
viewPosition: DICOMWeb.getString(instance['00185101']),
|
||||
numFrames: DICOMWeb.getNumber(instance['00280008']),
|
||||
frameRate: DICOMWeb.getNumber(instance['00181063'])
|
||||
frameRate: DICOMWeb.getNumber(instance['00181063']),
|
||||
sliceThickness: DICOMWeb.getNumber(instance['00180050'])
|
||||
};
|
||||
|
||||
if (server.imageRendering === 'wadouri') {
|
||||
|
||||
Reference in new issue
Block a user