From f349572d53b3242200a370b706ac0734486009a7 Mon Sep 17 00:00:00 2001 From: Erik Ziegler Date: Mon, 16 Nov 2015 17:49:35 +0100 Subject: [PATCH] Commented a lot of functions. Tried to get jsDoc and/or docco to work --- .gitignore | 2 + LesionTracker/client/routes.js | 5 +- Makefile | 13 ++ OHIFViewer/.gitignore | 1 + OHIFViewer/.meteor/versions | 1 + OHIFViewer/client/routes.js | 5 +- .../cornerstone/client/hangingProtocol.js | 11 ++ Packages/dicomweb/lib/findIndexOfString.js | 49 ++++++++ Packages/dicomweb/lib/uint8ArrayToString.js | 17 +++ Packages/dicomweb/package.js | 5 + .../imageViewerViewport.js | 6 +- .../viewer/layoutChooser/layoutChooser.js | 7 ++ .../viewportOrientationMarkers.js | 12 +- Packages/viewerbase/lib/createStacks.js | 18 +++ Packages/viewerbase/lib/docking-container.js | 2 - Packages/viewerbase/lib/encodeQueryData.js | 7 +- Packages/viewerbase/lib/findIndexOfString.js | 39 ------ .../lib/getActiveViewportElement.js | 5 - Packages/viewerbase/lib/getImageId.js | 6 + Packages/viewerbase/lib/getWADORSImageId.js | 7 ++ Packages/viewerbase/lib/helpers/formatDA.js | 6 +- .../lib/helpers/formatNumberPrecision.js | 5 +- Packages/viewerbase/lib/helpers/formatPN.js | 8 +- Packages/viewerbase/lib/helpers/formatTM.js | 9 +- Packages/viewerbase/lib/isTouchDevice.js | 8 ++ Packages/viewerbase/lib/metaDataProvider.js | 21 ++++ .../lib/rerenderViewportWithNewSeries.js | 6 +- Packages/viewerbase/lib/sortStudy.js | 6 + Packages/viewerbase/lib/stringToUint8Array.js | 8 +- Packages/viewerbase/lib/uint8ArrayToString.js | 9 -- .../lib/updateOrientationMarkers.js | 14 ++- Packages/viewerbase/log.js | 2 +- Packages/viewerbase/package.js | 9 -- .../server/methods/getStudyMetadata.js | 17 ++- .../server/methods/worklistSearch.js | 15 ++- .../server/services/qido/instances.js | 114 ++++++++++------- .../server/services/qido/studies.js | 83 +++++++------ .../server/services/wado/retrieveMetadata.js | 34 ++++- .../worklist/components/tabTitle/tabTitle.js | 45 +++++-- Packages/worklist/components/worklist.html | 4 +- Packages/worklist/components/worklist.js | 116 +++++++++++++++--- .../worklistResult/worklistResult.js | 7 ++ .../worklistSearch/worklistSearch.js | 110 +++++++++-------- Packages/worklist/lib/generateUUID.js | 5 +- Packages/worklist/package.js | 12 +- conf.json | 37 ++++++ 46 files changed, 654 insertions(+), 274 deletions(-) create mode 100644 Makefile create mode 100644 Packages/dicomweb/lib/findIndexOfString.js create mode 100644 Packages/dicomweb/lib/uint8ArrayToString.js delete mode 100644 Packages/viewerbase/lib/findIndexOfString.js delete mode 100644 Packages/viewerbase/lib/getActiveViewportElement.js delete mode 100644 Packages/viewerbase/lib/uint8ArrayToString.js create mode 100644 conf.json diff --git a/.gitignore b/.gitignore index b0746064f..efc739489 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +.idea/ +docs/ .meteor/local .meteor/meteorite node_modules \ No newline at end of file diff --git a/LesionTracker/client/routes.js b/LesionTracker/client/routes.js index 09a53ef6d..d283ee2e4 100644 --- a/LesionTracker/client/routes.js +++ b/LesionTracker/client/routes.js @@ -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; } diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..b7a100f68 --- /dev/null +++ b/Makefile @@ -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 diff --git a/OHIFViewer/.gitignore b/OHIFViewer/.gitignore index b0746064f..0d0d24eac 100644 --- a/OHIFViewer/.gitignore +++ b/OHIFViewer/.gitignore @@ -1,3 +1,4 @@ +.idea .meteor/local .meteor/meteorite node_modules \ No newline at end of file diff --git a/OHIFViewer/.meteor/versions b/OHIFViewer/.meteor/versions index 86aaf5f24..47b1c5a5a 100644 --- a/OHIFViewer/.meteor/versions +++ b/OHIFViewer/.meteor/versions @@ -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 diff --git a/OHIFViewer/client/routes.js b/OHIFViewer/client/routes.js index a40423c46..c05cebf2c 100644 --- a/OHIFViewer/client/routes.js +++ b/OHIFViewer/client/routes.js @@ -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; } diff --git a/Packages/cornerstone/client/hangingProtocol.js b/Packages/cornerstone/client/hangingProtocol.js index 0e90aa5c8..6f5195c87 100644 --- a/Packages/cornerstone/client/hangingProtocol.js +++ b/Packages/cornerstone/client/hangingProtocol.js @@ -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; diff --git a/Packages/dicomweb/lib/findIndexOfString.js b/Packages/dicomweb/lib/findIndexOfString.js new file mode 100644 index 000000000..37eaf61f3 --- /dev/null +++ b/Packages/dicomweb/lib/findIndexOfString.js @@ -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 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; +}; \ No newline at end of file diff --git a/Packages/dicomweb/lib/uint8ArrayToString.js b/Packages/dicomweb/lib/uint8ArrayToString.js new file mode 100644 index 000000000..35b85655d --- /dev/null +++ b/Packages/dicomweb/lib/uint8ArrayToString.js @@ -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; +}; diff --git a/Packages/dicomweb/package.js b/Packages/dicomweb/package.js index 79364e205..eb8b4f101 100644 --- a/Packages/dicomweb/package.js +++ b/Packages/dicomweb/package.js @@ -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'); }); diff --git a/Packages/viewerbase/components/viewer/imageViewerViewport/imageViewerViewport.js b/Packages/viewerbase/components/viewer/imageViewerViewport/imageViewerViewport.js index b0798fa3a..23826a2e8 100644 --- a/Packages/viewerbase/components/viewer/imageViewerViewport/imageViewerViewport.js +++ b/Packages/viewerbase/components/viewer/imageViewerViewport/imageViewerViewport.js @@ -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'); diff --git a/Packages/viewerbase/components/viewer/layoutChooser/layoutChooser.js b/Packages/viewerbase/components/viewer/layoutChooser/layoutChooser.js index 84e3ee81d..88a9e00dd 100644 --- a/Packages/viewerbase/components/viewer/layoutChooser/layoutChooser.js +++ b/Packages/viewerbase/components/viewer/layoutChooser/layoutChooser.js @@ -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'); diff --git a/Packages/viewerbase/components/viewer/viewportOrientationMarkers/viewportOrientationMarkers.js b/Packages/viewerbase/components/viewer/viewportOrientationMarkers/viewportOrientationMarkers.js index c7de59c96..c64676f4a 100644 --- a/Packages/viewerbase/components/viewer/viewportOrientationMarkers/viewportOrientationMarkers.js +++ b/Packages/viewerbase/components/viewer/viewportOrientationMarkers/viewportOrientationMarkers.js @@ -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); diff --git a/Packages/viewerbase/lib/createStacks.js b/Packages/viewerbase/lib/createStacks.js index b98bdca2e..2b5073803 100644 --- a/Packages/viewerbase/lib/createStacks.js +++ b/Packages/viewerbase/lib/createStacks.js @@ -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; diff --git a/Packages/viewerbase/lib/docking-container.js b/Packages/viewerbase/lib/docking-container.js index 5b7a1b4d3..9d4e6628b 100644 --- a/Packages/viewerbase/lib/docking-container.js +++ b/Packages/viewerbase/lib/docking-container.js @@ -1,6 +1,4 @@ (function($) { - - //Collapse the container function collapseContainer ( container ) { container.toggleClass("collapseHorizontal"); diff --git a/Packages/viewerbase/lib/encodeQueryData.js b/Packages/viewerbase/lib/encodeQueryData.js index 10355e65a..a97d5a6cf 100644 --- a/Packages/viewerbase/lib/encodeQueryData.js +++ b/Packages/viewerbase/lib/encodeQueryData.js @@ -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("&"); }; diff --git a/Packages/viewerbase/lib/findIndexOfString.js b/Packages/viewerbase/lib/findIndexOfString.js deleted file mode 100644 index 4b0746dd2..000000000 --- a/Packages/viewerbase/lib/findIndexOfString.js +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/Packages/viewerbase/lib/getActiveViewportElement.js b/Packages/viewerbase/lib/getActiveViewportElement.js deleted file mode 100644 index 1f71ff1db..000000000 --- a/Packages/viewerbase/lib/getActiveViewportElement.js +++ /dev/null @@ -1,5 +0,0 @@ -getActiveViewportElement = function() { - // TODO: Fix this, just hardcoded for now - var element = $('.imageViewerViewport').get(0); - return element; -}; \ No newline at end of file diff --git a/Packages/viewerbase/lib/getImageId.js b/Packages/viewerbase/lib/getImageId.js index bd75fe237..5c0122fc7 100644 --- a/Packages/viewerbase/lib/getImageId.js +++ b/Packages/viewerbase/lib/getImageId.js @@ -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; diff --git a/Packages/viewerbase/lib/getWADORSImageId.js b/Packages/viewerbase/lib/getWADORSImageId.js index 21dd3cc7f..df7049267 100644 --- a/Packages/viewerbase/lib/getWADORSImageId.js +++ b/Packages/viewerbase/lib/getWADORSImageId.js @@ -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; diff --git a/Packages/viewerbase/lib/helpers/formatDA.js b/Packages/viewerbase/lib/helpers/formatDA.js index 392014c5b..8c549d4f4 100644 --- a/Packages/viewerbase/lib/helpers/formatDA.js +++ b/Packages/viewerbase/lib/helpers/formatDA.js @@ -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; diff --git a/Packages/viewerbase/lib/helpers/formatNumberPrecision.js b/Packages/viewerbase/lib/helpers/formatNumberPrecision.js index 1cd0c0193..069718c09 100644 --- a/Packages/viewerbase/lib/helpers/formatNumberPrecision.js +++ b/Packages/viewerbase/lib/helpers/formatNumberPrecision.js @@ -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); } diff --git a/Packages/viewerbase/lib/helpers/formatPN.js b/Packages/viewerbase/lib/helpers/formatPN.js index 3c24d04e5..c2dc9db65 100644 --- a/Packages/viewerbase/lib/helpers/formatPN.js +++ b/Packages/viewerbase/lib/helpers/formatPN.js @@ -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('^', ', '); }); \ No newline at end of file diff --git a/Packages/viewerbase/lib/helpers/formatTM.js b/Packages/viewerbase/lib/helpers/formatTM.js index 842571724..01a113326 100644 --- a/Packages/viewerbase/lib/helpers/formatTM.js +++ b/Packages/viewerbase/lib/helpers/formatTM.js @@ -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); }); diff --git a/Packages/viewerbase/lib/isTouchDevice.js b/Packages/viewerbase/lib/isTouchDevice.js index 6a1fe84e9..b5db9cddd 100644 --- a/Packages/viewerbase/lib/isTouchDevice.js +++ b/Packages/viewerbase/lib/isTouchDevice.js @@ -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) || diff --git a/Packages/viewerbase/lib/metaDataProvider.js b/Packages/viewerbase/lib/metaDataProvider.js index 5b3f5292a..3a8171c4b 100644 --- a/Packages/viewerbase/lib/metaDataProvider.js +++ b/Packages/viewerbase/lib/metaDataProvider.js @@ -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) { diff --git a/Packages/viewerbase/lib/rerenderViewportWithNewSeries.js b/Packages/viewerbase/lib/rerenderViewportWithNewSeries.js index 35c6f3f16..6efbde61f 100644 --- a/Packages/viewerbase/lib/rerenderViewportWithNewSeries.js +++ b/Packages/viewerbase/lib/rerenderViewportWithNewSeries.js @@ -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 diff --git a/Packages/viewerbase/lib/sortStudy.js b/Packages/viewerbase/lib/sortStudy.js index adad39941..fae02eacc 100644 --- a/Packages/viewerbase/lib/sortStudy.js +++ b/Packages/viewerbase/lib/sortStudy.js @@ -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; diff --git a/Packages/viewerbase/lib/stringToUint8Array.js b/Packages/viewerbase/lib/stringToUint8Array.js index 484bbc82a..8b1378917 100644 --- a/Packages/viewerbase/lib/stringToUint8Array.js +++ b/Packages/viewerbase/lib/stringToUint8Array.js @@ -1,7 +1 @@ -stringToUint8Array = function(str) { - var uint=new Uint8Array(str.length); - for(var i=0,j=str.length;i Study List - {{ #each tabs }} + {{ #each worklistTabs }} {{ >tabTitle }} {{ /each }} @@ -14,7 +14,7 @@ {{> worklistResult }} - {{ #each tabs }} + {{ #each worklistTabs }} {{ >tabContent }} {{ /each }} diff --git a/Packages/worklist/components/worklist.js b/Packages/worklist/components/worklist.js index 3068a0741..267921307 100644 --- a/Packages/worklist/components/worklist.js +++ b/Packages/worklist/components/worklist.js @@ -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(); } }); diff --git a/Packages/worklist/components/worklistResult/worklistResult.js b/Packages/worklist/components/worklistResult/worklistResult.js index 52793abc6..ab75de242 100644 --- a/Packages/worklist/components/worklistResult/worklistResult.js +++ b/Packages/worklist/components/worklistResult/worklistResult.js @@ -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}}); } diff --git a/Packages/worklist/components/worklistSearch/worklistSearch.js b/Packages/worklist/components/worklistSearch/worklistSearch.js index af31b5f18..6f7c09514 100644 --- a/Packages/worklist/components/worklistSearch/worklistSearch.js +++ b/Packages/worklist/components/worklistSearch/worklistSearch.js @@ -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); } - - }); \ No newline at end of file diff --git a/Packages/worklist/lib/generateUUID.js b/Packages/worklist/lib/generateUUID.js index 3de8a8a46..7bc60c1b5 100644 --- a/Packages/worklist/lib/generateUUID.js +++ b/Packages/worklist/lib/generateUUID.js @@ -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) { diff --git a/Packages/worklist/package.js b/Packages/worklist/package.js index c97c87180..de7d3f7ba 100644 --- a/Packages/worklist/package.js +++ b/Packages/worklist/package.js @@ -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'); }); diff --git a/conf.json b/conf.json new file mode 100644 index 000000000..58c525cea --- /dev/null +++ b/conf.json @@ -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 + } +} \ No newline at end of file