From 9ccebb76bdffc1a96219af6938e60a5006cf9b20 Mon Sep 17 00:00:00 2001 From: Bruno Alves de Faria Date: Thu, 16 Mar 2017 17:09:46 -0300 Subject: [PATCH] Building the viewer data inside routes --- .../client/components/viewer/viewer.js | 2 +- LesionTracker/client/routes.js | 98 ++++---------- .../both/configuration/dataExchange.js | 4 +- .../lib/studylist/openNewTabWithTimepoint.js | 3 - Packages/ohif-lesiontracker/server/methods.js | 20 +-- .../both/configuration/timepoints.js | 4 +- Packages/ohif-viewerbase/client/index.js | 8 ++ .../client/lib/prepareViewerData.js | 122 ++++++++++++++++++ .../client/lib/renderViewer.js | 26 ++++ 9 files changed, 193 insertions(+), 94 deletions(-) create mode 100644 Packages/ohif-viewerbase/client/lib/prepareViewerData.js create mode 100644 Packages/ohif-viewerbase/client/lib/renderViewer.js diff --git a/LesionTracker/client/components/viewer/viewer.js b/LesionTracker/client/components/viewer/viewer.js index c9b26d3bd..4703cfecf 100644 --- a/LesionTracker/client/components/viewer/viewer.js +++ b/LesionTracker/client/components/viewer/viewer.js @@ -119,7 +119,7 @@ Template.viewer.onCreated(() => { instance.data.studies.forEach(study => (delete study.timepointType)); // TODO: Consider combining the retrieval calls into one? - const timepointsPromise = timepointApi.retrieveTimepoints(patientId); + const timepointsPromise = timepointApi.retrieveTimepoints({ patientId }); timepointsPromise.then(() => { const timepoints = timepointApi.all(); diff --git a/LesionTracker/client/routes.js b/LesionTracker/client/routes.js index 0f2fdb6c1..b3d2665a9 100644 --- a/LesionTracker/client/routes.js +++ b/LesionTracker/client/routes.js @@ -5,9 +5,6 @@ import { OHIF } from 'meteor/ohif:core'; // TODO: remove the line below window.Router = Router; -// verifyEmail controls whether emailVerification template will be rendered or not -const verifyEmail = Meteor.settings && Meteor.settings.public && Meteor.settings.public.verifyEmail || false; - Router.configure({ layoutTemplate: 'layout', loadingTemplate: 'layout' @@ -15,80 +12,37 @@ Router.configure({ Router.onBeforeAction('loading'); -const data = {}; - -const routerOptions = { data }; - -Router.route('/', { - name: 'home', - onBeforeAction: function() { - // Check if user needs to verify its email - if (verifyEmail && Meteor.user().emails && !Meteor.user().emails[0].verified) { - this.render('emailVerification', routerOptions); - } else { - this.render('app', routerOptions); - } - } -}); - -Router.route('/viewer/timepoints/:_id', { - layoutTemplate: 'layout', - name: 'viewerTimepoint', - onBeforeAction: function() { - const timepointId = this.params._id; - - this.render('app', routerOptions); - OHIF.lesiontracker.openNewTabWithTimepoint(timepointId); - } -}); - -OHIF.viewer.prepare = ({ studyInstanceUids, timepointId }) => { - // Clear the cornerstone tool data to sync the measurements with the measurements API - cornerstoneTools.globalImageIdSpecificToolStateManager = cornerstoneTools.newImageIdSpecificToolStateManager(); - - return new Promise((resolve, reject) => { - OHIF.studylist.retrieveStudiesMetadata(studyInstanceUids).then(studies => { - // Add additional metadata to our study from the studylist - studies.forEach(study => { - const studylistStudy = OHIF.studylist.collections.Studies.findOne({ - studyInstanceUid: study.studyInstanceUid - }); - - if (!studylistStudy) { - return; - } - - Object.assign(study, studylistStudy); - }); - - resolve(studies); - }).catch(reject); - }); -}; - -Router.route('/viewer/studies/:studyInstanceUids', { - name: 'viewerStudies', - onBeforeAction: function() { - this.render('app', { data: { template: 'loadingText' } }); - - const studyInstanceUids = this.params.studyInstanceUids.split(';'); - OHIF.viewer.prepare({ studyInstanceUids }).then(studies => { - this.render('app', { - data: { - template: 'viewer', - studies - } - }); - }); - } -}); - Router.onBeforeAction(function() { + // verifyEmail controls whether emailVerification template will be rendered or not + const publicSettings = Meteor.settings && Meteor.settings.public; + const verifyEmail = publicSettings && publicSettings.verifyEmail || false; + + // Check if user is signed in or needs an email verification if (!Meteor.userId() && !Meteor.loggingIn()) { this.render('entrySignIn'); + } else if (verifyEmail && Meteor.user().emails && !Meteor.user().emails[0].verified) { + this.render('emailVerification'); } else { this.next(); } }, { - except: ['entrySignIn', 'entrySignUp', 'forgotPassword', 'resetPassword'] + except: ['entrySignIn', 'entrySignUp', 'forgotPassword', 'resetPassword', 'emailVerification'] }); + +Router.route('/', function() { + this.redirect('/studylist'); +}, { name: 'home' }); + +Router.route('/studylist', function() { + this.render('app', { data: { template: 'studylist' } }); +}, { name: 'studylist' }); + +Router.route('/viewer/timepoints/:timepointId', function() { + const timepointId = this.params.timepointId; + OHIF.viewerbase.renderViewer(this, { timepointId }); +}, { name: 'viewerTimepoint' }); + +Router.route('/viewer/studies/:studyInstanceUids', function() { + const studyInstanceUids = this.params.studyInstanceUids.split(';'); + OHIF.viewerbase.renderViewer(this, { studyInstanceUids }); +}, { name: 'viewerStudies' }); diff --git a/Packages/ohif-lesiontracker/both/configuration/dataExchange.js b/Packages/ohif-lesiontracker/both/configuration/dataExchange.js index 460c2f462..f8fea4ea1 100644 --- a/Packages/ohif-lesiontracker/both/configuration/dataExchange.js +++ b/Packages/ohif-lesiontracker/both/configuration/dataExchange.js @@ -36,11 +36,11 @@ export const storeMeasurements = (measurementData, timepointIds) => { }); }; -export const retrieveTimepoints = (patientId) => { +export const retrieveTimepoints = filter => { console.log('retrieveTimepoints'); return new Promise((resolve, reject) => { - Meteor.call('retrieveTimepoints', patientId, (error, response) => { + Meteor.call('retrieveTimepoints', filter, (error, response) => { if (error) { reject(error); } else { diff --git a/Packages/ohif-lesiontracker/client/lib/studylist/openNewTabWithTimepoint.js b/Packages/ohif-lesiontracker/client/lib/studylist/openNewTabWithTimepoint.js index d020b192b..c34e1a026 100644 --- a/Packages/ohif-lesiontracker/client/lib/studylist/openNewTabWithTimepoint.js +++ b/Packages/ohif-lesiontracker/client/lib/studylist/openNewTabWithTimepoint.js @@ -8,8 +8,6 @@ import { OHIF } from 'meteor/ohif:core'; * @param title The title to be used for the tab heading */ OHIF.lesiontracker.openNewTabWithTimepoint = timepointId => { - const contentId = 'viewerTab'; - const Timepoints = OHIF.studylist.timepointApi.timepoints; const timepoint = Timepoints.findOne({ timepointId: timepointId @@ -27,7 +25,6 @@ OHIF.lesiontracker.openNewTabWithTimepoint = timepointId => { // Update the OHIF.viewer.data global object OHIF.viewer.data = { - contentId: contentId, studyInstanceUids: data.studyInstanceUids, timepointIds: data.timepointIds, currentTimepointId: timepointId diff --git a/Packages/ohif-lesiontracker/server/methods.js b/Packages/ohif-lesiontracker/server/methods.js index 45054e391..cbb4db934 100644 --- a/Packages/ohif-lesiontracker/server/methods.js +++ b/Packages/ohif-lesiontracker/server/methods.js @@ -44,11 +44,11 @@ Meteor.methods({ disassociateStudy(timepointIds, studyInstanceUid) { OHIF.log.info('Disassociating Study from Timepoints'); timepointIds.forEach(timepointId => { - const timepoint = Timepoints.findOne({timepointId}); + const timepoint = Timepoints.findOne({ timepointId }); if (!timepoint) { return; } - + // Find the index of the current studyInstanceUid in the array // of reference studyInstanceUids const index = timepoint.studyInstanceUids.indexOf(studyInstanceUid); @@ -59,8 +59,6 @@ Meteor.methods({ // Remove the specified studyInstanceUid from the array of associated studyInstanceUids timepoint.studyInstanceUids.splice(index, 1); - let updated = []; - let removed = []; if (timepoint.studyInstanceUids.length) { Timepoints.update(timepoint._id, { $set: { @@ -78,14 +76,14 @@ Meteor.methods({ timepointId: timepointId }; - MeasurementCollections[tool.id].remove(filter) + MeasurementCollections[tool.id].remove(filter); }); }); }, removeTimepoint(timepointId) { OHIF.log.info('Removing Timepoint from the Server'); - Timepoints.remove({timepointId}); + Timepoints.remove({ timepointId }); }, updateTimepoint(timepointData, query) { @@ -95,14 +93,8 @@ Meteor.methods({ Timepoints.update(timepointData, query); }, - retrieveTimepoints(patientId) { + retrieveTimepoints(filter={}) { OHIF.log.info('Retrieving Timepoints from the Server'); - - const filter = {} - if (patientId) { - filter.patientId = patientId; - }; - return Timepoints.find(filter).fetch(); }, @@ -128,7 +120,7 @@ Meteor.methods({ OHIF.log.info('Retrieving Measurements from the Server'); let measurementData = {}; - const filter = {} + const filter = {}; if (patientId) { filter.patientId = patientId; } diff --git a/Packages/ohif-measurements/both/configuration/timepoints.js b/Packages/ohif-measurements/both/configuration/timepoints.js index fe4610316..f15479135 100644 --- a/Packages/ohif-measurements/both/configuration/timepoints.js +++ b/Packages/ohif-measurements/both/configuration/timepoints.js @@ -26,14 +26,14 @@ class TimepointApi { this.timepoints._debugName = 'Timepoints'; } - retrieveTimepoints(patientId) { + retrieveTimepoints(filter) { const retrievalFn = configuration.dataExchange.retrieve; if (!_.isFunction(retrievalFn)) { return; } return new Promise((resolve, reject) => { - retrievalFn(patientId).then(timepointData => { + retrievalFn(filter).then(timepointData => { OHIF.log.info('Timepoint data retrieval'); OHIF.log.info(timepointData); _.each(timepointData, timepoint => { diff --git a/Packages/ohif-viewerbase/client/index.js b/Packages/ohif-viewerbase/client/index.js index 0cc115d49..0282a73ef 100644 --- a/Packages/ohif-viewerbase/client/index.js +++ b/Packages/ohif-viewerbase/client/index.js @@ -92,6 +92,14 @@ Viewerbase.imageViewerViewportData = imageViewerViewportData; import { panelNavigation } from './lib/panelNavigation'; Viewerbase.panelNavigation = panelNavigation; +// prepareViewerData +import { prepareViewerData } from './lib/prepareViewerData'; +Viewerbase.prepareViewerData = prepareViewerData; + +// renderViewer +import { renderViewer } from './lib/renderViewer'; +Viewerbase.renderViewer = renderViewer; + // WLPresets.* import { WLPresets } from './lib/WLPresets'; Viewerbase.wlPresets = WLPresets; diff --git a/Packages/ohif-viewerbase/client/lib/prepareViewerData.js b/Packages/ohif-viewerbase/client/lib/prepareViewerData.js new file mode 100644 index 000000000..7366b2f97 --- /dev/null +++ b/Packages/ohif-viewerbase/client/lib/prepareViewerData.js @@ -0,0 +1,122 @@ +import { OHIF } from 'meteor/ohif:core'; + +/** + * Prepare the studies data to render the viewer template + * + * @param {Array} studyInstanceUids List of studies that will be loaded into viewer + * @param {String} timepointId ID of the current timepoint to get the studies from + * @param {Object} timepointsFilter An object containing the filter to retrieve the timepoints + * @return {Promise} Promise that will be resolved with the studies when the metadata is loaded + */ +export const prepareViewerData = ({ studyInstanceUids, timepointId, timepointsFilter={} }) => { + // Clear the cornerstone tool data to sync the measurements with the measurements API + cornerstoneTools.globalImageIdSpecificToolStateManager = cornerstoneTools.newImageIdSpecificToolStateManager(); + + // Retrieve the studies metadata + const promise = new Promise((resolve, reject) => { + const processData = viewerData => { + OHIF.studylist.retrieveStudiesMetadata(viewerData.studyInstanceUids).then(studies => { + // Add additional metadata to our study from the studylist + studies.forEach(study => { + const studylistStudy = OHIF.studylist.collections.Studies.findOne({ + studyInstanceUid: study.studyInstanceUid + }); + + if (!studylistStudy) { + return; + } + + Object.assign(study, studylistStudy); + }); + + resolve({ + studies, + viewerData + }); + }).catch(reject); + }; + + // Check if the studies are already given and ignore the timepoint ID if so + if (studyInstanceUids && studyInstanceUids.length) { + const viewerData = { studyInstanceUids }; + processData(viewerData); + } else { + // Find the timepoint by ID and load the studies from it + OHIF.studylist.timepointApi.retrieveTimepoints(timepointsFilter).then(() => { + const viewerData = buildViewerDataFromTimepointId(timepointId); + processData(viewerData); + }).catch(reject); + } + }); + + return promise; +}; + +const buildViewerDataFromTimepointId = timepointId => { + const timepoint = OHIF.studylist.timepointApi.timepoints.findOne({ timepointId }); + if (!timepoint) { + throw new Error('Unable to find a time point with the given ID'); + } + + // Get the relevant studyInstanceUids given the timepoints + const data = getDataFromTimepoint(timepoint); + if (!data.studyInstanceUids) { + throw new Error('No studies found that are related to this timepoint'); + } + + // Build the viewer data and return it + return Object.assign(data, { currentTimepointId: timepointId }); +}; + +/** + * Retrieves related studies given a Baseline or Follow-up Timepoint + * + * @param {Object} timepoint A document from the Timepoints Collection + * @returns {Object} An object containing the related studies UIDs and timepoint IDs + */ +const getDataFromTimepoint = timepoint => { + let relatedStudies = timepoint.studyInstanceUids; + + // If this is the baseline, we should stop here and return the relevant studies + if (isBaseline(timepoint)) { + return { + studyInstanceUids: relatedStudies, + timepointIds: [timepoint.timepointId] + }; + } + + // Otherwise, this is a follow-up exam, so we should also find the baseline timepoint, + // and all studies related to it. We also enforce that the Baseline should have a studyDate + // prior to the latest studyDate in the current (Follow-up) Timepoint. + const Timepoints = OHIF.studylist.timepointApi.timepoints; + const baseline = Timepoints.findOne({ + timepointType: 'baseline', + patientId: timepoint.patientId, + latestDate: { + $lte: timepoint.latestDate + } + }); + + let timepointIds = []; + if (baseline) { + relatedStudies = relatedStudies.concat(baseline.studyInstanceUids); + timepointIds.push(baseline.timepointId); + } else { + OHIF.log.warn('No Baseline found while opening a Follow-up Timepoint'); + } + + timepointIds.push(timepoint.timepointId); + + return { + studyInstanceUids: relatedStudies, + timepointIds: timepointIds + }; +}; + +/** + * Checks if a Timepoints is a baseline or not + * + * @param {Object} timepoint A document from the Timepoints Collection + * @returns {boolean} Whether or not the timepoint is stored as a Baseline + */ +const isBaseline = timepoint => timepoint.timepointType === 'baseline'; diff --git a/Packages/ohif-viewerbase/client/lib/renderViewer.js b/Packages/ohif-viewerbase/client/lib/renderViewer.js new file mode 100644 index 000000000..90851f043 --- /dev/null +++ b/Packages/ohif-viewerbase/client/lib/renderViewer.js @@ -0,0 +1,26 @@ +import { OHIF } from 'meteor/ohif:core'; + +/** + * Render the viewer with the given routing context and parameters + * + * @param {Context} context Context of the router + * @param {Object} params Parameters that will be used to prepare the viewer data + */ +export const renderViewer = (context, params) => { + // Wait until the viewer data is ready to render it + const promise = OHIF.viewerbase.prepareViewerData(params); + + // Show loading state while preparing the viewer data + OHIF.ui.showDialog('dialogLoading', { promise }); + + // Render the viewer when the data is ready + promise.then(({ studies, viewerData }) => { + context.render('app', { + data: { + template: 'viewer', + studies, + viewerData + } + }); + }); +};