Commented a lot of functions. Tried to get jsDoc and/or docco to work
This commit is contained in:
parent
14f47ad0e3
commit
f349572d53
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,3 +1,5 @@
|
||||
.idea/
|
||||
docs/
|
||||
.meteor/local
|
||||
.meteor/meteorite
|
||||
node_modules
|
||||
@ -1,4 +1,3 @@
|
||||
tabs = new Meteor.Collection(null);
|
||||
Session.setDefault('ViewerData', {});
|
||||
|
||||
// Re-add any tab data saved in the Session
|
||||
@ -8,7 +7,7 @@ Object.keys(ViewerData).forEach(function(contentId) {
|
||||
title: tabData.title,
|
||||
contentid: tabData.contentid,
|
||||
};
|
||||
tabs.insert(data);
|
||||
WorklistTabs.insert(data);
|
||||
});
|
||||
|
||||
Router.configure({
|
||||
@ -34,7 +33,7 @@ Router.route('/viewer/:_id', {
|
||||
|
||||
// Check if this study is already loaded in a tab
|
||||
// If it is, stop here so we don't keep adding tabs on hot-code reloads
|
||||
var tab = tabs.find({'studyInstanceUid' : studyInstanceUid}).fetch();
|
||||
var tab = WorklistTabs.find({'studyInstanceUid' : studyInstanceUid}).fetch();
|
||||
if (tab) {
|
||||
return;
|
||||
}
|
||||
|
||||
13
Makefile
Normal file
13
Makefile
Normal file
@ -0,0 +1,13 @@
|
||||
docs:
|
||||
mkdir -p docs
|
||||
rm docs/* || true
|
||||
|
||||
# Make jsdoc3 documentation
|
||||
jsdoc . -c conf.json -r -d docs -R README.md
|
||||
|
||||
# Make docco documentation (not sure if we will keep this)
|
||||
mkdir -p docs/docco
|
||||
docco {Packages,Viewer,LesionTracker}/{**/*,*} -o docs/docco -l parallel
|
||||
|
||||
# Force rebuild even if the docs exist
|
||||
.PHONY: docs
|
||||
1
OHIFViewer/.gitignore
vendored
1
OHIFViewer/.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
.idea
|
||||
.meteor/local
|
||||
.meteor/meteorite
|
||||
node_modules
|
||||
@ -58,6 +58,7 @@ minifiers@1.1.7
|
||||
minimongo@1.0.10
|
||||
mobile-experience@1.0.1
|
||||
mobile-status-bar@1.0.6
|
||||
momentjs:moment@2.8.4
|
||||
mongo@1.1.3
|
||||
mongo-id@1.0.1
|
||||
mrt:moment@2.8.1
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
tabs = new Meteor.Collection(null);
|
||||
Session.setDefault('ViewerData', {});
|
||||
|
||||
// Re-add any tab data saved in the Session
|
||||
@ -8,7 +7,7 @@ Object.keys(ViewerData).forEach(function(contentId) {
|
||||
title: tabData.title,
|
||||
contentid: tabData.contentid,
|
||||
};
|
||||
tabs.insert(data);
|
||||
WorklistTabs.insert(data);
|
||||
});
|
||||
|
||||
Router.configure({
|
||||
@ -34,7 +33,7 @@ Router.route('/viewer/:_id', {
|
||||
|
||||
// Check if this study is already loaded in a tab
|
||||
// If it is, stop here so we don't keep adding tabs on hot-code reloads
|
||||
var tab = tabs.find({studyInstanceUid: studyInstanceUid}).fetch();
|
||||
var tab = WorklistTabs.find({studyInstanceUid: studyInstanceUid}).fetch();
|
||||
if (tab) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1,5 +1,16 @@
|
||||
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) {
|
||||
var studies = inputData.studies;
|
||||
var viewportRows = inputData.viewportRows;
|
||||
|
||||
49
Packages/dicomweb/lib/findIndexOfString.js
Normal file
49
Packages/dicomweb/lib/findIndexOfString.js
Normal file
@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Converts a String to a UInt8 array of character codes
|
||||
* @param {String} str Input string
|
||||
* @returns {Uint8Array} Uint8 Array of character codes
|
||||
*/
|
||||
function stringToUint8Array(str) {
|
||||
var uint=new Uint8Array(str.length);
|
||||
for(var i=0,j=str.length;i<j;i++){
|
||||
uint[i]=str.charCodeAt(i);
|
||||
}
|
||||
return uint;
|
||||
}
|
||||
|
||||
function checkToken(token, data, dataOffset) {
|
||||
if(dataOffset + token.length > data.length) {
|
||||
//console.log('dataOffset >> ', dataOffset);
|
||||
return false;
|
||||
}
|
||||
|
||||
for(var i = 0; i < token.length; i++) {
|
||||
if(token[i] !== data[endIndex++]) {
|
||||
if(endIndex > 520000) {
|
||||
console.log('token=',uint8ArrayToString(token));
|
||||
console.log('data=', uint8ArrayToString(data, dataOffset, endIndex-dataOffset));
|
||||
console.log('miss at %d %s dataOffset=%d', i, String.fromCharCode(data[endIndex]), endIndex);
|
||||
console.log('miss at %d %s dataOffset=%d', i, String.fromCharCode(token[endIndex]), endIndex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
findIndexOfString = function(data, str, offset) {
|
||||
offset = offset || 0;
|
||||
|
||||
var token = stringToUint8Array(str);
|
||||
|
||||
for(var i=offset; i < data.length; i++) {
|
||||
if(data[i] === token[0]) {
|
||||
//console.log('match @', i);
|
||||
if(checkToken(token, data, i)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
17
Packages/dicomweb/lib/uint8ArrayToString.js
Normal file
17
Packages/dicomweb/lib/uint8ArrayToString.js
Normal file
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Converts a Uint8 array to a string
|
||||
* @param data
|
||||
* @param offset
|
||||
* @param length
|
||||
* @returns {string}
|
||||
*/
|
||||
uint8ArrayToString = function(data, offset, length) {
|
||||
offset = offset || 0;
|
||||
length = length || data.length - offset;
|
||||
var str = "";
|
||||
|
||||
for(var i=offset; i < offset + length; i++) {
|
||||
str += String.fromCharCode(data[i]);
|
||||
}
|
||||
return str;
|
||||
};
|
||||
@ -7,6 +7,7 @@ Package.describe({
|
||||
Package.onUse(function (api) {
|
||||
api.use('http');
|
||||
|
||||
// DICOMWeb API functions
|
||||
api.addFiles('server/namespace.js', 'server');
|
||||
api.addFiles('server/getImageFrame.js', 'server');
|
||||
api.addFiles('server/getJSON.js', 'server');
|
||||
@ -14,6 +15,10 @@ Package.onUse(function (api) {
|
||||
api.addFiles('server/getNumber.js', 'server');
|
||||
api.addFiles('server/getString.js', 'server');
|
||||
|
||||
// Helper functions
|
||||
api.addFiles('lib/findIndexOfString.js', 'server');
|
||||
api.addFiles('lib/uint8ArrayToString.js', 'server');
|
||||
|
||||
api.export("DICOMWeb", 'server');
|
||||
});
|
||||
|
||||
|
||||
@ -190,9 +190,11 @@ function loadSeriesIntoViewport(data) {
|
||||
// Add stack state managers for the stack tool, CINE tool, and reference lines
|
||||
cornerstoneTools.addStackStateManager(element, [ 'stack', 'playClip', 'referenceLines' ]);
|
||||
|
||||
// Enable orientation markers, if applicable
|
||||
// Get the current viewport settings
|
||||
var viewport = cornerstone.getViewport(element);
|
||||
updateOrientationMarkers(element, viewport);
|
||||
|
||||
// Enable orientation markers, if applicable
|
||||
updateOrientationMarkers(element);
|
||||
|
||||
// Clear any old stack data
|
||||
cornerstoneTools.clearToolState(element, 'stack');
|
||||
|
||||
@ -1,3 +1,10 @@
|
||||
/**
|
||||
* Adds the 'hover' class to cells above and to the left of the current cell
|
||||
* This is used to "fill in" the grid that the user will change the layout to,
|
||||
* if they click on a specific table cell.
|
||||
*
|
||||
* @param currentCell
|
||||
*/
|
||||
function highlightCells(currentCell) {
|
||||
var cells = $('.layoutChooser table td');
|
||||
cells.removeClass('hover');
|
||||
|
||||
@ -1,5 +1,13 @@
|
||||
updateOrientationMarkers = function(element, viewport) {
|
||||
// Updates the orientation labels on the viewport
|
||||
/**
|
||||
* Updates the orientation labels on a Cornerstone-enabled Viewport element
|
||||
* when the viewport settings change (e.g. when a horizontal flip or a rotation occurs)
|
||||
*
|
||||
* @param element The DOM element of the Cornerstone viewport
|
||||
*/
|
||||
updateOrientationMarkers = function(element) {
|
||||
// Get the current viewport settings
|
||||
var viewport = cornerstone.getViewport(element);
|
||||
|
||||
var enabledElement = cornerstone.getEnabledElement(element);
|
||||
var imagePlane = cornerstoneTools.metaData.get('imagePlane', enabledElement.image.imageId);
|
||||
|
||||
|
||||
@ -1,7 +1,22 @@
|
||||
/**
|
||||
* Creates a set of series to be placed in the Study Browser
|
||||
* The series that appear in the Study Browser must represent
|
||||
* imaging modalities.
|
||||
*
|
||||
* Furthermore, for drag/drop functionality,
|
||||
* it is easiest if the stack objects also contain information about
|
||||
* which study they are linked to.
|
||||
*
|
||||
* @param study The study instance to be used
|
||||
* @returns {Array} An array of series to be placed in the Study Browser
|
||||
*/
|
||||
createStacks = function(study) {
|
||||
// Define an empty array of stacks
|
||||
var stacks = [];
|
||||
|
||||
// TODO: Split by multi-frame, modality, image size, etc
|
||||
study.seriesList.forEach(function(series) {
|
||||
// If the series has no instanced, skip it
|
||||
if (!series.instances) {
|
||||
return;
|
||||
}
|
||||
@ -13,7 +28,10 @@ createStacks = function(study) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Include the study instance Uid for drag/drop purposes
|
||||
series.studyInstanceUid = study.studyInstanceUid;
|
||||
|
||||
// Add this series to the list of stacks
|
||||
stacks.push(series);
|
||||
});
|
||||
return stacks;
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
(function($) {
|
||||
|
||||
|
||||
//Collapse the container
|
||||
function collapseContainer ( container ) {
|
||||
container.toggleClass("collapseHorizontal");
|
||||
|
||||
@ -8,10 +8,11 @@
|
||||
encodeQueryData = function(data)
|
||||
{
|
||||
var ret = [];
|
||||
|
||||
for (var d in data) {
|
||||
if(data[d]) {
|
||||
ret.push(encodeURIComponent(d) + "=" + encodeURIComponent(data[d]));
|
||||
}
|
||||
if(data[d]) {
|
||||
ret.push(encodeURIComponent(d) + "=" + encodeURIComponent(data[d]));
|
||||
}
|
||||
}
|
||||
return ret.join("&");
|
||||
};
|
||||
|
||||
@ -1,39 +0,0 @@
|
||||
function checkToken(token, data, dataOffset) {
|
||||
|
||||
if(dataOffset + token.length > data.length) {
|
||||
//console.log('dataOffset >> ', dataOffset);
|
||||
return false;
|
||||
}
|
||||
|
||||
var endIndex = dataOffset;
|
||||
|
||||
for(var i = 0; i < token.length; i++) {
|
||||
if(token[i] !== data[endIndex++]) {
|
||||
if(endIndex > 520000) {
|
||||
console.log('token=',uint8ArrayToString(token));
|
||||
console.log('data=', uint8ArrayToString(data, dataOffset, endIndex-dataOffset));
|
||||
console.log('miss at %d %s dataOffset=%d', i, String.fromCharCode(data[endIndex]), endIndex);
|
||||
console.log('miss at %d %s dataOffset=%d', i, String.fromCharCode(token[endIndex]), endIndex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
findIndexOfString = function(data, str, offset) {
|
||||
|
||||
offset = offset || 0;
|
||||
|
||||
var token = stringToUint8Array(str);
|
||||
|
||||
for(var i=offset; i < data.length; i++) {
|
||||
if(token[0] === data[i]) {
|
||||
//console.log('match @', i);
|
||||
if(checkToken(token, data, i)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
getActiveViewportElement = function() {
|
||||
// TODO: Fix this, just hardcoded for now
|
||||
var element = $('.imageViewerViewport').get(0);
|
||||
return element;
|
||||
};
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* Obtain an imageId for Cornerstone from an image instance
|
||||
*
|
||||
* @param instance
|
||||
* @returns {string} The imageId to be used by Cornerstone
|
||||
*/
|
||||
getImageId = function(instance) {
|
||||
if (!instance) {
|
||||
return;
|
||||
|
||||
@ -1,3 +1,10 @@
|
||||
/**
|
||||
* Obtain an imageId for Cornerstone based on the WADO-RS scheme
|
||||
*
|
||||
* @param instance
|
||||
* @returns {string} The imageId to be used by Cornerstone
|
||||
*/
|
||||
|
||||
getWADORSImageId = function(instance) {
|
||||
var columnPixelSpacing = 1.0;
|
||||
var rowPixelSpacing = 1.0;
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
/**
|
||||
* A global Blaze UI helper function to format DICOM Dates using the Moment library
|
||||
*/
|
||||
UI.registerHelper('formatDA', function (context, format, options) {
|
||||
if (!context) {
|
||||
return undefined;
|
||||
}
|
||||
var date = context;
|
||||
var dateAsMoment = moment(date, "YYYYMMDD");
|
||||
var dateAsMoment = moment(context, "YYYYMMDD");
|
||||
var strFormat = "MMM D, YYYY";
|
||||
if (options) {
|
||||
strFormat = format;
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
UI.registerHelper('formatNumberPrecision', function(context, precision, options) {
|
||||
/**
|
||||
* A global Blaze UI helper to format a float value to a specified precision
|
||||
*/
|
||||
UI.registerHelper('formatNumberPrecision', function(context, precision) {
|
||||
if (context) {
|
||||
return parseFloat(context).toFixed(precision);
|
||||
}
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
UI.registerHelper('formatPN', function (context, format, options) {
|
||||
/**
|
||||
* A global Blaze UI helper to format a patient name for display purposes
|
||||
*/
|
||||
UI.registerHelper('formatPN', function (context) {
|
||||
if (!context) {
|
||||
return undefined;
|
||||
}
|
||||
var patientName = context.replace('^', ', ');
|
||||
return patientName;
|
||||
return context.replace('^', ', ');
|
||||
});
|
||||
@ -1,13 +1,14 @@
|
||||
UI.registerHelper('formatTM', function(context, format, options) {
|
||||
/**
|
||||
* A global Blaze UI helper to format a DICOM Time for display using the Moment library
|
||||
*/
|
||||
UI.registerHelper('formatTM', function(context, format) {
|
||||
if (!context) {
|
||||
return undefined;
|
||||
}
|
||||
var date = context;
|
||||
var dateAsMoment = moment(date);
|
||||
var dateAsMoment = moment(context);
|
||||
var strFormat = "HH:mm:ss";
|
||||
if (options) {
|
||||
strFormat = format;
|
||||
}
|
||||
|
||||
return dateAsMoment.format(strFormat);
|
||||
});
|
||||
|
||||
@ -1,3 +1,11 @@
|
||||
/**
|
||||
* 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) ||
|
||||
|
||||
@ -1,5 +1,17 @@
|
||||
var metaDataLookup = {};
|
||||
|
||||
/**
|
||||
* Cornerstone MetaData provider to store image meta data
|
||||
* Data from instances, series, and studies are associated with
|
||||
* imageIds to facilitate usage of this information by Cornerstone's Tools
|
||||
*
|
||||
* e.g. the imagePlane metaData object contains instance information about
|
||||
* row/column pixel spacing, patient position, and patient orientation. It
|
||||
* is used in CornerstoneTools to position reference lines and orientation markers.
|
||||
*
|
||||
* @param {String} imageId The Cornerstone ImageId
|
||||
* @param {Object} data An object containing instance, series, and study metaData
|
||||
*/
|
||||
addMetaData = function(imageId, data) {
|
||||
var instanceMetaData = data.instance;
|
||||
var seriesMetaData = data.series;
|
||||
@ -72,6 +84,15 @@ addMetaData = function(imageId, data) {
|
||||
metaDataLookup[imageId] = metaData;
|
||||
};
|
||||
|
||||
/**
|
||||
* Looks up metaData for Cornerstone Tools given a specified type and imageId
|
||||
* A type may be, e.g. 'study', or 'patient', or 'imagePlane'. These types
|
||||
* are keys in the stored metaData objects.
|
||||
*
|
||||
* @param type
|
||||
* @param imageId
|
||||
* @returns {Object} Relevant metaData of the specified type
|
||||
*/
|
||||
function provider(type, imageId) {
|
||||
var imageMetaData = metaDataLookup[imageId];
|
||||
if (!imageMetaData) {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* This function destroys and rerenders the imageViewerViewport template.
|
||||
* This function destroys and re-renders the imageViewerViewport template.
|
||||
* It uses the data provided (object containing seriesIndex and studyIndex) to
|
||||
* load a new series into the produced viewport.
|
||||
*
|
||||
@ -7,8 +7,12 @@
|
||||
* @param data An object containing a seriesIndex and studyIndex for a study to load into this viewport
|
||||
*/
|
||||
rerenderViewportWithNewSeries = function(element, data) {
|
||||
// Get the container and index of the current viewport.
|
||||
// The parent container is identified because it is later removed from the DOM
|
||||
var container = $(element).parents('.viewportContainer').get(0);
|
||||
var viewportIndex = $(container).index();
|
||||
|
||||
// Record the current viewportIndex so this can be passed into the re-rendering call
|
||||
data.viewportIndex = viewportIndex;
|
||||
|
||||
// Update the dictionary of loaded series for the specified viewport
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* Sorts the series and instances inside a study instance by their series
|
||||
* and instance numbers in ascending order.
|
||||
*
|
||||
* @param {Object} study The study instance
|
||||
*/
|
||||
sortStudy = function(study) {
|
||||
if (!study) {
|
||||
return;
|
||||
|
||||
@ -1,7 +1 @@
|
||||
stringToUint8Array = function(str) {
|
||||
var uint=new Uint8Array(str.length);
|
||||
for(var i=0,j=str.length;i<j;i++){
|
||||
uint[i]=str.charCodeAt(i);
|
||||
}
|
||||
return uint;
|
||||
};
|
||||
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
uint8ArrayToString = function(data, offset, length) {
|
||||
offset = offset || 0;
|
||||
length = length || data.length - offset;
|
||||
var str = "";
|
||||
for(var i=offset; i < offset + length; i++) {
|
||||
str += String.fromCharCode(data[i]);
|
||||
}
|
||||
return str;
|
||||
};
|
||||
@ -1,5 +1,13 @@
|
||||
updateOrientationMarkers = function(element, viewport) {
|
||||
// Updates the orientation labels on the viewport
|
||||
/**
|
||||
* Updates the orientation labels on a Cornerstone-enabled Viewport element
|
||||
* when the viewport settings change (e.g. when a horizontal flip or a rotation occurs)
|
||||
*
|
||||
* @param element The DOM element of the Cornerstone viewport
|
||||
*/
|
||||
updateOrientationMarkers = function(element) {
|
||||
// Get the current viewport settings
|
||||
var viewport = cornerstone.getViewport(element);
|
||||
|
||||
var enabledElement = cornerstone.getEnabledElement(element);
|
||||
var imagePlane = cornerstoneTools.metaData.get('imagePlane', enabledElement.image.imageId);
|
||||
|
||||
@ -14,7 +22,7 @@ updateOrientationMarkers = function(element, viewport) {
|
||||
|
||||
var markers = {
|
||||
top: oppositeColumnString,
|
||||
left: oppositeRowString,
|
||||
left: oppositeRowString
|
||||
};
|
||||
|
||||
// If any vertical or horizontal flips are applied, change the orientation strings ahead of
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
// Create package logger using loglevel
|
||||
// https://atmospherejs.com/spacejamio/loglevel
|
||||
log = loglevel.createPackageLogger('viewerbase', defaultLevel = 'warn');
|
||||
log = loglevel.createPackageLogger('viewerbase', defaultLevel = 'info');
|
||||
@ -95,33 +95,24 @@ Package.onUse(function (api) {
|
||||
api.addFiles('lib/accountsConfig.js', 'client');
|
||||
api.addFiles('lib/createStacks.js', 'client');
|
||||
api.addFiles('lib/encodeQueryData.js', ['client', 'server']);
|
||||
api.addFiles('lib/findIndexOfString.js', 'client');
|
||||
api.addFiles('lib/getActiveViewportElement.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');
|
||||
api.addFiles('lib/stringToUint8Array.js', 'client');
|
||||
api.addFiles('lib/toolManager.js', 'client');
|
||||
api.addFiles('lib/uint8ArrayToString.js', 'client');
|
||||
api.addFiles('lib/updateOrientationMarkers.js', 'client');
|
||||
|
||||
//api.export('accountsConfig', 'client');
|
||||
api.export('createStacks', 'client');
|
||||
api.export('encodeQueryData', ['client', 'server']);
|
||||
api.export('findIndexOfString', 'client');
|
||||
api.export('getActiveViewportElement', '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');
|
||||
api.export('stringToUint8Array', 'client');
|
||||
api.export('toolManager', 'client');
|
||||
api.export('uint8ArrayToString', 'client');
|
||||
api.export('updateOrientationMarkers', 'client');
|
||||
|
||||
// UI Helpers
|
||||
|
||||
@ -1,10 +1,15 @@
|
||||
Meteor.methods({
|
||||
'GetStudyMetadata' : function(studyInstanceUid) {
|
||||
log.info('GetStudyMetadata(%s)', studyInstanceUid);
|
||||
/**
|
||||
* Retrieves Study metadata given a Study Instance UID
|
||||
* This Meteor method is available from both the client and the server
|
||||
*/
|
||||
'GetStudyMetadata' : function(studyInstanceUid) {
|
||||
log.info('GetStudyMetadata(%s)', studyInstanceUid);
|
||||
|
||||
var server = Meteor.settings.dicomWeb.endpoints[0];
|
||||
// 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.Instances(server, studyInstanceUid);
|
||||
return Services.WADO.RetrieveMetadata(server, studyInstanceUid);
|
||||
}
|
||||
return Services.WADO.RetrieveMetadata(server, studyInstanceUid);
|
||||
}
|
||||
});
|
||||
@ -1,6 +1,13 @@
|
||||
Meteor.methods({
|
||||
'WorklistSearch' : function(filter) {
|
||||
var server = Meteor.settings.dicomWeb.endpoints[0];
|
||||
return Services.QIDO.Studies(server, filter);
|
||||
}
|
||||
/**
|
||||
* Use the specified filter to conduct a search from the DICOM server
|
||||
* @param filter
|
||||
*/
|
||||
'WorklistSearch': function(filter) {
|
||||
// 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);
|
||||
}
|
||||
});
|
||||
@ -1,59 +1,83 @@
|
||||
/**
|
||||
* Creates a QIDO URL given the server settings and a study instance UID
|
||||
* @param server
|
||||
* @param studyInstanceUid
|
||||
* @returns {string} URL to be used for QIDO calls
|
||||
*/
|
||||
function buildUrl(server, studyInstanceUid) {
|
||||
|
||||
var url = server.qidoRoot + '/studies/' + studyInstanceUid + '/instances';
|
||||
|
||||
return url;
|
||||
return server.qidoRoot + '/studies/' + studyInstanceUid + '/instances';
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses data returned from a QIDO search and transforms it into
|
||||
* an array of series that are present in the study
|
||||
*
|
||||
* @param server The DICOM server
|
||||
* @param studyInstanceUid
|
||||
* @param resultData
|
||||
* @returns {Array} Series List
|
||||
*/
|
||||
function resultDataToStudyMetadata(server, studyInstanceUid, resultData) {
|
||||
var seriesMap = {};
|
||||
var seriesList = [];
|
||||
resultData.forEach(function(instance) {
|
||||
var seriesInstanceUid = DICOMWeb.getString(instance['0020000E']);
|
||||
var series = seriesMap[seriesInstanceUid];
|
||||
if(!series) {
|
||||
series = {
|
||||
seriesInstanceUid : seriesInstanceUid,
|
||||
seriesNumber : DICOMWeb.getString(instance['00200011']),
|
||||
instances: []
|
||||
};
|
||||
seriesMap[seriesInstanceUid] = series;
|
||||
seriesList.push(series);
|
||||
}
|
||||
var seriesMap = {};
|
||||
var seriesList = [];
|
||||
|
||||
// The uri for the dicomweb
|
||||
// NOTE: DCM4CHEE seems to return the data zipped
|
||||
// NOTE: Orthanc returns the data with multi-part mime which cornerstoneWADOImageLoader doesn't
|
||||
// know how to parse yet
|
||||
//var uri = DICOMWeb.getString(instance['00081190']);
|
||||
//uri = uri.replace('wado-rs', 'dicom-web');
|
||||
resultData.forEach(function(instance) {
|
||||
// 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 = DICOMWeb.getString(instance['0020000E']);
|
||||
var series = seriesMap[seriesInstanceUid];
|
||||
|
||||
// manually create a WADO-URI from the UID's
|
||||
// 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";
|
||||
// If no series data exists in the seriesMap cache variable,
|
||||
// process any available series data
|
||||
if(!series) {
|
||||
series = {
|
||||
seriesInstanceUid : seriesInstanceUid,
|
||||
seriesNumber : DICOMWeb.getString(instance['00200011']),
|
||||
instances: []
|
||||
};
|
||||
|
||||
series.instances.push({
|
||||
sopClassUid: DICOMWeb.getString(instance['00080016']),
|
||||
sopInstanceUid: sopInstanceUid,
|
||||
uri: uri,
|
||||
instanceNumber: DICOMWeb.getString(instance['00200013'])
|
||||
});
|
||||
// Save this data in the seriesMap cache variable
|
||||
seriesMap[seriesInstanceUid] = series;
|
||||
seriesList.push(series);
|
||||
}
|
||||
|
||||
// The uri for the dicomweb
|
||||
// NOTE: DCM4CHEE seems to return the data zipped
|
||||
// NOTE: Orthanc returns the data with multi-part mime which cornerstoneWADOImageLoader doesn't
|
||||
// know how to parse yet
|
||||
//var uri = DICOMWeb.getString(instance['00081190']);
|
||||
//uri = uri.replace('wado-rs', 'dicom-web');
|
||||
|
||||
// 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";
|
||||
|
||||
// Add this instance to the current series
|
||||
series.instances.push({
|
||||
sopClassUid: DICOMWeb.getString(instance['00080016']),
|
||||
sopInstanceUid: sopInstanceUid,
|
||||
uri: uri,
|
||||
instanceNumber: DICOMWeb.getString(instance['00200013'])
|
||||
});
|
||||
});
|
||||
return seriesList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a set of instances using a QIDO call
|
||||
* @param server
|
||||
* @param studyInstanceUid
|
||||
* @returns {{wadoUriRoot: String, studyInstanceUid: String, seriesList: Array}}
|
||||
*/
|
||||
Services.QIDO.Instances = function(server, studyInstanceUid) {
|
||||
var url = buildUrl(server, studyInstanceUid);
|
||||
var result = DICOMWeb.getJSON(url, server.requestOptions);
|
||||
|
||||
var url = buildUrl(server, studyInstanceUid);
|
||||
|
||||
var result = DICOMWeb.getJSON(url, server.requestOptions);
|
||||
|
||||
var study = {
|
||||
wadoUriRoot: server.wadoUriRoot,
|
||||
studyInstanceUid: studyInstanceUid,
|
||||
seriesList: resultDataToStudyMetadata(server, studyInstanceUid, result.data)
|
||||
};
|
||||
|
||||
return study;
|
||||
return {
|
||||
wadoUriRoot: server.wadoUriRoot,
|
||||
studyInstanceUid: studyInstanceUid,
|
||||
seriesList: resultDataToStudyMetadata(server, studyInstanceUid, result.data)
|
||||
};
|
||||
};
|
||||
@ -1,45 +1,56 @@
|
||||
/**
|
||||
* Produces a QIDO URL given server details and a set of specified search filter
|
||||
* items
|
||||
*
|
||||
* @param server
|
||||
* @param filter
|
||||
* @returns {string} The URL with encoded filter query data
|
||||
*/
|
||||
function filterToQIDOURL(server, filter) {
|
||||
var parameters = {
|
||||
PatientName : filter.patientName,
|
||||
PatientID: filter.patientId,
|
||||
AccessionNumber : filter.accessionNumber,
|
||||
limit : filter.limit || 20,
|
||||
includefield : server.qidoSupportsIncludeField ? 'all' : undefined
|
||||
};
|
||||
var url = server.qidoRoot + '/studies?' + encodeQueryData(parameters);
|
||||
return url;
|
||||
var parameters = {
|
||||
PatientName : filter.patientName,
|
||||
PatientID: filter.patientId,
|
||||
AccessionNumber : filter.accessionNumber,
|
||||
limit : filter.limit || 20,
|
||||
includefield : server.qidoSupportsIncludeField ? 'all' : undefined
|
||||
};
|
||||
return server.qidoRoot + '/studies?' + encodeQueryData(parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(study) {
|
||||
studies.push({
|
||||
studyInstanceUid: DICOMWeb.getString(study['0020000D']),
|
||||
// 00080005 = SpecificCharacterSet
|
||||
studyDate: DICOMWeb.getString(study['00080020']),
|
||||
studyTime: DICOMWeb.getString(study['00080030']),
|
||||
accessionNumber: DICOMWeb.getString(study['00080050']),
|
||||
referringPhysicianName: DICOMWeb.getString(study['00080090']),
|
||||
// 00081190 = URL
|
||||
patientName: DICOMWeb.getName(study['00100010']),
|
||||
patientId: DICOMWeb.getString(study['00100020']),
|
||||
patientBirthdate: DICOMWeb.getString(study['00100030']),
|
||||
patientSex: DICOMWeb.getString(study['00100040']),
|
||||
imageCount: DICOMWeb.getString(study['00201208']),
|
||||
studyId: DICOMWeb.getString(study['00200010']),
|
||||
studyDescription: DICOMWeb.getString(study['00081030']),
|
||||
modalities: DICOMWeb.getString(study['00080061'])
|
||||
//url : server.wadoRoot + '/studies/' + DICOMWeb.getString(study['0020000D']) + '/metadata'
|
||||
var studies = [];
|
||||
|
||||
resultData.forEach(function(study) {
|
||||
studies.push({
|
||||
studyInstanceUid: DICOMWeb.getString(study['0020000D']),
|
||||
// 00080005 = SpecificCharacterSet
|
||||
studyDate: DICOMWeb.getString(study['00080020']),
|
||||
studyTime: DICOMWeb.getString(study['00080030']),
|
||||
accessionNumber: DICOMWeb.getString(study['00080050']),
|
||||
referringPhysicianName: DICOMWeb.getString(study['00080090']),
|
||||
// 00081190 = URL
|
||||
patientName: DICOMWeb.getName(study['00100010']),
|
||||
patientId: DICOMWeb.getString(study['00100020']),
|
||||
patientBirthdate: DICOMWeb.getString(study['00100030']),
|
||||
patientSex: DICOMWeb.getString(study['00100040']),
|
||||
imageCount: DICOMWeb.getString(study['00201208']),
|
||||
studyId: DICOMWeb.getString(study['00200010']),
|
||||
studyDescription: DICOMWeb.getString(study['00081030']),
|
||||
modalities: DICOMWeb.getString(study['00080061'])
|
||||
});
|
||||
});
|
||||
});
|
||||
return studies;
|
||||
return studies;
|
||||
}
|
||||
|
||||
|
||||
Services.QIDO.Studies = function(server, filter) {
|
||||
|
||||
var url = filterToQIDOURL(server, filter);
|
||||
|
||||
var result = DICOMWeb.getJSON(url, server.requestOptions);
|
||||
|
||||
return resultDataToStudies(result.data);
|
||||
var url = filterToQIDOURL(server, filter);
|
||||
var result = DICOMWeb.getJSON(url, server.requestOptions);
|
||||
return resultDataToStudies(result.data);
|
||||
};
|
||||
@ -1,8 +1,22 @@
|
||||
/**
|
||||
* Creates a URL for a WADO search
|
||||
*
|
||||
* @param server
|
||||
* @param studyInstanceUid
|
||||
* @returns {string}
|
||||
*/
|
||||
function buildUrl(server, studyInstanceUid) {
|
||||
var url = server.wadoRoot + '/studies/' + studyInstanceUid + '/metadata';
|
||||
return url;
|
||||
return server.wadoRoot + '/studies/' + studyInstanceUid + '/metadata';
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@ -13,6 +27,16 @@ function getSourceImageInstanceUid(instance) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses result data from a WADO search into Study MetaData
|
||||
* Returns an object populated with study metadata, including the
|
||||
* series list.
|
||||
*
|
||||
* @param server
|
||||
* @param studyInstanceUid
|
||||
* @param resultData
|
||||
* @returns {{seriesList: Array, patientName: *, patientId: *, accessionNumber: *, studyDate: *, modalities: *, studyDescription: *, imageCount: *, studyInstanceUid: *}}
|
||||
*/
|
||||
function resultDataToStudyMetadata(server, studyInstanceUid, resultData) {
|
||||
var seriesMap = {};
|
||||
var seriesList = [];
|
||||
@ -92,6 +116,12 @@ function resultDataToStudyMetadata(server, studyInstanceUid, resultData) {
|
||||
return studyData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieved Study MetaData from a DICOM server using a WADO call
|
||||
* @param server
|
||||
* @param studyInstanceUid
|
||||
* @returns {{seriesList: Array, patientName: *, patientId: *, accessionNumber: *, studyDate: *, modalities: *, studyDescription: *, imageCount: *, studyInstanceUid: *}}
|
||||
*/
|
||||
Services.WADO.RetrieveMetadata = function(server, studyInstanceUid) {
|
||||
var url = buildUrl(server, studyInstanceUid);
|
||||
|
||||
|
||||
@ -1,19 +1,46 @@
|
||||
Template.tabTitle.events({
|
||||
/**
|
||||
* Closes a tab when the close button is pressed in the title
|
||||
* The next tab to the left is loaded if the current tab is closed
|
||||
*
|
||||
* @param e The click event used to close the tab
|
||||
*/
|
||||
'click .close': function(e) {
|
||||
// Identify the tab title DOM node
|
||||
var tab = $(e.currentTarget).parents('a[data-toggle="tab"]').eq(0);
|
||||
|
||||
// Get the relevent contentId that this tab title represents
|
||||
// Replace any hash marks (#) that were required by Bootstrap's tab switching
|
||||
var contentId = tab.data("target").replace("#", "");
|
||||
|
||||
// Check if we are closing the active tab. If we are, prepare to switch
|
||||
// to the next tab to the left.
|
||||
var activeContentId = Session.get('activeContentId');
|
||||
if (activeContentId === contentId) {
|
||||
// Find the index of the tab that is being closed
|
||||
var tabIndex = tab.parent('li').index();
|
||||
|
||||
var tabIndex = tab.parent('li').index();
|
||||
var newActiveTabIndex = Math.max(tabIndex - 1, 0);
|
||||
var newActiveTab = $(".tabTitle").eq(newActiveTabIndex);
|
||||
var newActiveTabLink = newActiveTab.find("a[data-toggle=tab]");
|
||||
var newContentId = newActiveTabLink.data("target").replace("#", "");
|
||||
// Find the index the tab to its left
|
||||
var newActiveTabIndex = Math.max(tabIndex - 1, 0);
|
||||
|
||||
switchToTab(newContentId);
|
||||
|
||||
var tabObjectId = tabs.find({contentid: contentId}).fetch()[0]._id;
|
||||
tabs.remove(tabObjectId);
|
||||
// Find the DOM node of the tab that will be activated
|
||||
var newActiveTab = $(".tabTitle").eq(newActiveTabIndex);
|
||||
|
||||
// Find the content ID of the tab that will be switched to
|
||||
var newActiveTabLink = newActiveTab.find("a[data-toggle=tab]");
|
||||
var newContentId = newActiveTabLink.data("target").replace("#", "");
|
||||
|
||||
// Switch to this tab
|
||||
switchToTab(newContentId);
|
||||
}
|
||||
|
||||
// Find the tab to be closed in the Tabs collection
|
||||
var tabObjectId = WorklistTabs.findOne({contentid: contentId})._id;
|
||||
|
||||
// Remove this tab from the Tabs collection so it is no longer rendered
|
||||
WorklistTabs.remove(tabObjectId);
|
||||
|
||||
// Remove any stored data related to this tab from the global ViewerData structure
|
||||
delete ViewerData[contentId];
|
||||
}
|
||||
});
|
||||
@ -3,7 +3,7 @@
|
||||
<li role="presentation" class="tabTitle active">
|
||||
<a data-target="#worklistTab" role="tab" data-toggle="tab">Study List</a>
|
||||
</li>
|
||||
{{ #each tabs }}
|
||||
{{ #each worklistTabs }}
|
||||
{{ >tabTitle }}
|
||||
{{ /each }}
|
||||
</ul>
|
||||
@ -14,7 +14,7 @@
|
||||
{{> worklistResult }}
|
||||
</div>
|
||||
</div>
|
||||
{{ #each tabs }}
|
||||
{{ #each worklistTabs }}
|
||||
{{ >tabContent }}
|
||||
{{ /each }}
|
||||
</div>
|
||||
|
||||
@ -1,31 +1,78 @@
|
||||
ViewerData = Session.get('ViewerData') || {};
|
||||
StudyMetaData = {};
|
||||
/**
|
||||
* Template: Worklist
|
||||
*
|
||||
* This is the main component of the Worklist package
|
||||
*/
|
||||
|
||||
// Define the ViewerData global object
|
||||
// If there is currently any Session data for this object,
|
||||
// use this to repopulate the variable
|
||||
ViewerData = Session.get('ViewerData') || {};
|
||||
|
||||
// Define the StudyMetaData object. This is used as a cache
|
||||
// to store study meta data information to prevent unnecessary
|
||||
// calls to the server
|
||||
var StudyMetaData = {};
|
||||
|
||||
// Create the WorklistTabs collection
|
||||
WorklistTabs = new Meteor.Collection(null);
|
||||
|
||||
/**
|
||||
* Retrieves study metadata using a server call, and fires a callback
|
||||
* when completed.
|
||||
*
|
||||
* @params {string} studyInstanceUid The UID of the Study to be retrieved
|
||||
* @params {function} doneCallback The callback function to be executed when the study retrieval has finished
|
||||
*/
|
||||
getStudyMetadata = function(studyInstanceUid, doneCallback) {
|
||||
log.info('worklistStudy getStudyMetadata');
|
||||
|
||||
if (StudyMetaData.hasOwnProperty(studyInstanceUid) && StudyMetaData[studyInstanceUid]) {
|
||||
var study = StudyMetaData[studyInstanceUid];
|
||||
// If the StudyMetaData cache already has data related to this
|
||||
// studyInstanceUid, then we should fire the doneCallback with this data
|
||||
// and stop here.
|
||||
var study = StudyMetaData[studyInstanceUid];
|
||||
if (study) {
|
||||
doneCallback(study);
|
||||
return;
|
||||
}
|
||||
|
||||
// If no study metadata is in the cache variable, we need to retrieve it from
|
||||
// the server with a call.
|
||||
Meteor.call('GetStudyMetadata', studyInstanceUid, function(error, study) {
|
||||
// Once we have retrieved the data, we sort the series' by series
|
||||
// and instance number in ascending order
|
||||
sortStudy(study);
|
||||
|
||||
|
||||
// Then we store this data in the cache variable
|
||||
StudyMetaData[studyInstanceUid] = study;
|
||||
|
||||
// Finally, we fire the doneCallback with this study meta data
|
||||
doneCallback(study);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Switches to a new tab in the tabbed worklist container
|
||||
* This function renders either the Worklist or the Viewer template with new data.
|
||||
*
|
||||
* @param contentId The unique ID of the tab to be switched to
|
||||
*/
|
||||
switchToTab = function(contentId) {
|
||||
log.info("Switching to tab: " + contentId);
|
||||
|
||||
// Use Bootstrap's Tab JavaScript to show the contents of the current tab
|
||||
// Unless it is the worklist, it is currently an empty div
|
||||
$('.tabTitle a[data-target="#' + contentId + '"]').tab('show');
|
||||
|
||||
// Remove any previous Viewers from the DOM
|
||||
$("#viewer").remove();
|
||||
|
||||
// Update the 'activeContentId' variable in Session
|
||||
Session.set("activeContentId", contentId);
|
||||
|
||||
// If we are switching to the Worklist tab, reset any CSS styles
|
||||
// that have been applied to prevent scrolling in the Viewer.
|
||||
// Then stop here, since nothing needs to be re-rendered.
|
||||
if (contentId === 'worklistTab') {
|
||||
document.body.style.overflow = null;
|
||||
document.body.style.height = null;
|
||||
@ -34,30 +81,44 @@ switchToTab = function(contentId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set active tab
|
||||
Session.set("activeContentId", contentId);
|
||||
|
||||
// Get tab content container
|
||||
// Get tab content container given the contentId string
|
||||
// If no such container exists, stop here because something is wrong
|
||||
var container = $('.tab-content').find("#" + contentId).get(0);
|
||||
if (!container) {
|
||||
log.warn('No container present with the contentId: ' + contentId);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Use the stored ViewerData global object to retrieve the studyInstanceUid
|
||||
// related to this tab
|
||||
var studyInstanceUid = ViewerData[contentId].studyInstanceUid;
|
||||
|
||||
// Attempt to retrieve the meta data (it might be cached)
|
||||
getStudyMetadata(studyInstanceUid, function(study) {
|
||||
|
||||
// Once we have the study data, store it in a structure with
|
||||
// any other saved data about this tab (e.g. layout structure)
|
||||
var data = {
|
||||
viewportRows: ViewerData[contentId].viewportRows,
|
||||
viewportColumns: ViewerData[contentId].viewportColumns,
|
||||
contentId: contentId,
|
||||
studies: [study],
|
||||
studies: [study]
|
||||
};
|
||||
|
||||
// Update the Session variable with the loaded studies
|
||||
Session.set('studies', data.studies);
|
||||
|
||||
// Remove the loading text template that is inside by default
|
||||
// Remove the loading text template that is inside the tab container by default
|
||||
container.innerHTML = "";
|
||||
|
||||
// Use Blaze to render the Viewer Template into the container
|
||||
UI.renderWithData(Template.viewer, data, container);
|
||||
|
||||
// Retrieve the DOM element of the viewer
|
||||
var imageViewer = $("#viewer");
|
||||
|
||||
// If it is present in the DOM (it should be), then apply
|
||||
// styles to prevent page scrolling and overscrolling on mobile devices
|
||||
if (imageViewer) {
|
||||
document.body.style.overflow = "hidden";
|
||||
document.body.style.height = '100%';
|
||||
@ -68,20 +129,34 @@ switchToTab = function(contentId) {
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Opens a new tab in the tabbed worklist environment using
|
||||
* a given study and new tab title.
|
||||
*
|
||||
* @param studyInstanceUid The UID of the Study to be opened
|
||||
* @param title The title to be used for the tab heading
|
||||
*/
|
||||
openNewTab = function(studyInstanceUid, title) {
|
||||
// Generate a unique ID to represent this tab
|
||||
// We can't just use the Mongo entry ID because
|
||||
// then it will change after hot-reloading.
|
||||
var contentid = generateUUID();
|
||||
var data = {
|
||||
|
||||
// Create a new entry in the WorklistTabs Collection
|
||||
WorklistTabs.insert({
|
||||
title: title,
|
||||
contentid: contentid,
|
||||
};
|
||||
tabs.insert(data);
|
||||
active: false
|
||||
});
|
||||
|
||||
// Update the ViewerData global object
|
||||
ViewerData[contentid] = {
|
||||
title: title,
|
||||
contentid: contentid,
|
||||
studyInstanceUid: studyInstanceUid
|
||||
};
|
||||
|
||||
// Switch to the new tab
|
||||
switchToTab(contentid);
|
||||
};
|
||||
|
||||
@ -96,9 +171,12 @@ Template.worklist.onRendered(function() {
|
||||
|
||||
|
||||
Template.worklist.helpers({
|
||||
'tabs': function() {
|
||||
log.info('Updating tabs');
|
||||
return tabs.find();
|
||||
/**
|
||||
* Returns the current set of Worklist Tabs
|
||||
* @returns Meteor.Collection The current state of the WorklistTabs Collection
|
||||
*/
|
||||
'worklistTabs': function() {
|
||||
return WorklistTabs.find();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -1,6 +1,13 @@
|
||||
// Define the Studies Collection
|
||||
// This is a client-side only Collection which
|
||||
// Stores the list of studies in the Worklist
|
||||
Studies = new Mongo.Collection(null);
|
||||
|
||||
Template.worklistResult.helpers({
|
||||
/**
|
||||
* Returns a sorted instance of the Studies Collection
|
||||
* by Patient name and Study Date in Ascending order.
|
||||
*/
|
||||
studies : function() {
|
||||
return Studies.find({}, {sort: {patientName : 1, studyDate : 1}});
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
//Get all studies
|
||||
// Retrieve all studies
|
||||
search();
|
||||
|
||||
var studyDateFrom;
|
||||
@ -6,6 +6,13 @@ var studyDateTo;
|
||||
var checkFrom = false;
|
||||
var checkTo = false;
|
||||
|
||||
/**
|
||||
* Transforms an input string into a search filter for
|
||||
* the Worklist Search call
|
||||
*
|
||||
* @param filter The input string to be searched for
|
||||
* @returns {*}
|
||||
*/
|
||||
function getFilter(filter) {
|
||||
if(filter && filter.length && filter.substr(filter.length - 1) !== '*') {
|
||||
filter += '*';
|
||||
@ -13,16 +20,19 @@ function getFilter(filter) {
|
||||
return filter;
|
||||
}
|
||||
|
||||
//Search value
|
||||
/**
|
||||
* Search for a value in a string
|
||||
*/
|
||||
function isIndexOf(mainVal, searchVal) {
|
||||
if(mainVal === undefined || mainVal === '' || mainVal.indexOf(searchVal) > -1){
|
||||
if (mainVal === undefined || mainVal === '' || mainVal.indexOf(searchVal) > -1){
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//Replace object is undefined
|
||||
/**
|
||||
* Replace object if undefined
|
||||
*/
|
||||
function replaceUndefinedColumnValue (text) {
|
||||
if (text == undefined || text === "undefined") {
|
||||
return "";
|
||||
@ -31,94 +41,96 @@ function replaceUndefinedColumnValue (text) {
|
||||
}
|
||||
}
|
||||
|
||||
//Convert string to study date
|
||||
/**
|
||||
* Convert string to study date
|
||||
*/
|
||||
function convertStringToStudyDate (dateStr) {
|
||||
var y = dateStr.substring(0,4);
|
||||
var m = dateStr.substring(4,6);
|
||||
var d = dateStr.substring(6,8);
|
||||
var newDateStr = y+"/"+m+"/"+d;
|
||||
var date_ = new Date(newDateStr);
|
||||
return date_;
|
||||
return new Date(newDateStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a search for studies matching the worklist query parameters
|
||||
* Inserts the identified studies into the Studies Collection
|
||||
*/
|
||||
function search() {
|
||||
// Create the filters to be used for the Worklist Search
|
||||
var filter = {
|
||||
patientName: getFilter($('#patientName').val()),
|
||||
patientId: getFilter($('#patientId').val()),
|
||||
accessionNumber: getFilter($('#patientAccessionNumber').val())
|
||||
};
|
||||
|
||||
// Make sure that modality has a reasonable value, since it is occasionally
|
||||
// returned as 'undefined'
|
||||
var modality = replaceUndefinedColumnValue($('#modality').val());
|
||||
|
||||
// Clear all current studies
|
||||
Studies.remove({});
|
||||
|
||||
Meteor.call('WorklistSearch', filter, function(error, studies) {
|
||||
if (studies) {
|
||||
studies.forEach(function(study) {
|
||||
|
||||
if(isIndexOf(study.modalities, modality) &&
|
||||
(new Date(studyDateFrom).setHours(0,0,0,0) <= convertStringToStudyDate(study.studyDate) || !checkFrom) &&
|
||||
(convertStringToStudyDate(study.studyDate) <= new Date(studyDateTo).setHours(0,0,0,0) || !checkTo)) {
|
||||
|
||||
Studies.insert(study);
|
||||
}
|
||||
});
|
||||
if (!studies) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Loop through all identified studies
|
||||
studies.forEach(function(study) {
|
||||
|
||||
// Search the rest of the parameters that aren't done via the server call
|
||||
if(isIndexOf(study.modalities, modality) &&
|
||||
(new Date(studyDateFrom).setHours(0,0,0,0) <= convertStringToStudyDate(study.studyDate) || !checkFrom) &&
|
||||
(convertStringToStudyDate(study.studyDate) <= new Date(studyDateTo).setHours(0,0,0,0) || !checkTo)) {
|
||||
|
||||
// Insert any matching studies into the Studies Collection
|
||||
Studies.insert(study);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
Template.worklistSearch.events({
|
||||
|
||||
//Search
|
||||
/**
|
||||
* Searches for studies given the current state of the form controls
|
||||
*/
|
||||
'click button#btnSearch' :function(event) {
|
||||
studyDateFrom = $('#studyDateFrom').val();
|
||||
studyDateTo = $('#studyDateTo').val();
|
||||
checkFrom = $('#checkFrom').is(':checked');
|
||||
checkTo = $('#checkTo').is(':checked');
|
||||
search();
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
//Today
|
||||
/**
|
||||
* Searches for studies with a study date equal to Today
|
||||
*/
|
||||
'click button#btnToday' :function(event) {
|
||||
studyDateTo = new Date();
|
||||
studyDateFrom = studyDateTo;
|
||||
checkFrom = true;
|
||||
checkTo = true;
|
||||
|
||||
search();
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
//Clear
|
||||
'click button#btnClear' :function(event) {
|
||||
$("#patientId").val("");
|
||||
$("#patientName").val("");
|
||||
$("#patientAccessionNumber").val("");
|
||||
$("#studyDescription").val("");
|
||||
$("#referringPhysician").val("");
|
||||
$("#studyDateFrom").val("");
|
||||
$("#studyDateTo").val("");
|
||||
$("#modality").val("");
|
||||
$('#checkFrom').prop('checked', false);
|
||||
$('#checkTo').prop('checked', false);
|
||||
|
||||
},
|
||||
|
||||
//Last 7 Days
|
||||
'click button#btnLastSevenDays' :function(event) {
|
||||
/**
|
||||
* Searches for studies in the Last 7 Days
|
||||
*/
|
||||
'click button#btnLastSevenDays' :function() {
|
||||
studyDateTo = new Date();
|
||||
studyDateFrom = new Date();
|
||||
studyDateFrom.setDate(studyDateFrom.getDate()-7);
|
||||
checkFrom = true;
|
||||
checkTo = true;
|
||||
|
||||
search();
|
||||
|
||||
return false;
|
||||
},
|
||||
/**
|
||||
* Clears all input data in the study search panel
|
||||
*/
|
||||
'click button#btnClear' :function(event) {
|
||||
$("#searchPanel .form-control").val("");
|
||||
$('#checkFrom').prop('checked', false);
|
||||
$('#checkTo').prop('checked', false);
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
@ -1,4 +1,7 @@
|
||||
//Generate UUID to create unique tabs
|
||||
/**
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
generateUUID = function() {
|
||||
var d = new Date().getTime();
|
||||
var uuid = 'xxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
|
||||
@ -14,9 +14,12 @@ Package.onUse(function (api) {
|
||||
|
||||
api.use('practicalmeteor:loglevel');
|
||||
|
||||
// Our custom package
|
||||
// Our custom packages
|
||||
api.use('dicomweb');
|
||||
|
||||
// This sets the default logging level of the package using the
|
||||
// loglevel package. It can be overridden in the JavaScript
|
||||
// console for debugging purposes
|
||||
api.addFiles('log.js', 'client');
|
||||
|
||||
// Components
|
||||
@ -46,10 +49,15 @@ Package.onUse(function (api) {
|
||||
api.addFiles('lib/generateUUID.js', 'client');
|
||||
api.export('generateUUID', 'client');
|
||||
|
||||
// Export Worklist helper functions for usage in Routes
|
||||
api.export('getStudyMetadata', 'client');
|
||||
api.export('openNewTab', 'client');
|
||||
api.export('switchToTab', 'client');
|
||||
|
||||
|
||||
// Export the global ViewerData object
|
||||
api.export('ViewerData', 'client');
|
||||
|
||||
// Export the WorklistTabs Collection
|
||||
api.export('WorklistTabs', 'client');
|
||||
});
|
||||
|
||||
|
||||
37
conf.json
Normal file
37
conf.json
Normal file
@ -0,0 +1,37 @@
|
||||
// Run with jsdoc . -r -c conf.json -d docs
|
||||
{
|
||||
"tags": {
|
||||
"allowUnknownTags": true,
|
||||
"dictionaries": ["jsdoc"]
|
||||
},
|
||||
"source": {
|
||||
"include": ["Packages", "OHIFViewer", "LesionTracker"],
|
||||
"includePattern": ".+\\.js(doc)?$",
|
||||
"excludePattern": "(^|\\/|\\\\)_",
|
||||
"exclude": ["Packages/cornerstone/", "node_modules", "docs", "etc"]
|
||||
},
|
||||
"plugins": ["plugins/markdown"],
|
||||
"templates": {
|
||||
"logoFile": "",
|
||||
"cleverLinks": false,
|
||||
"monospaceLinks": false,
|
||||
"dateFormat": "ddd MMM Do YYYY",
|
||||
"outputSourceFiles": false,
|
||||
"outputSourcePath": false,
|
||||
"systemName": "OHIF Meteor Packages",
|
||||
"footer": "OHIF",
|
||||
"copyright": "Copyright © 2015 Open Health Imaging Foundation",
|
||||
"navType": "vertical",
|
||||
"theme": "flatly",
|
||||
"linenums": true,
|
||||
"collapseSymbols": false,
|
||||
"inverseNav": true,
|
||||
"highlightTutorialCode": true,
|
||||
"protocol": "html://",
|
||||
"methodHeadingReturns": false
|
||||
},
|
||||
"markdown": {
|
||||
"parser": "gfm",
|
||||
"hardwrap": true
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user