From 9cde04bcfec3960525660b3815a48a6bf602a738 Mon Sep 17 00:00:00 2001 From: Erik Ziegler Date: Tue, 9 Feb 2016 23:46:04 +0100 Subject: [PATCH] Remove some subscription / reactivity / caching bugs and the unnecessary PatientLocations collection --- LesionTracker/client/components/viewer.html | 8 ++- LesionTracker/client/components/viewer.js | 26 +++++-- LesionTracker/client/debugReactivity.js | 68 +++++++++++++++++++ .../client/collections/LesionLocations.js | 1 + .../client/collections/LocationResponses.js | 1 + .../client/collections/PatientLocations.js | 1 - .../client/compatibility/lesionTool.js | 5 +- .../client/compatibility/nonTargetTool.js | 2 +- .../associationModal/associationModal.js | 61 +++++++++++++---- .../lesionLocationDialog.js | 18 +---- .../lesionTrackerWorklistContextMenu.js | 8 +-- .../nonTargetLesionDialog.js | 44 ++++-------- .../components/studyDateList/studyDateList.js | 7 +- .../timepointTextDialog.html | 12 ---- .../timepointTextDialog.styl | 25 ------- Packages/lesiontracker/lib/LesionManager.js | 25 +------ .../lib/TrialCriteriaConstraints.js | 12 ++-- .../lib/TrialResponseCriteria.js | 20 +++--- .../lesiontracker/lib/activateMeasurements.js | 4 ++ Packages/lesiontracker/lib/clearTools.js | 16 +++-- .../lib/handleMeasurementAdded.js | 2 +- .../lib/handleMeasurementModified.js | 2 +- .../lib/syncMeasurementAndToolData.js | 2 +- .../lib/worklist/worklistModification.js | 3 + Packages/lesiontracker/package.js | 5 -- Packages/lesiontracker/server/collections.js | 6 ++ .../lesiontracker/server/removeCollections.js | 3 + .../imageThumbnail/imageThumbnail.js | 4 +- .../hangingProtocolButtons.js | 6 +- .../imageViewerViewport.js | 13 +++- .../imageViewerViewports.js | 41 +++++++---- .../server/services/dimse/instances.js | 2 +- .../components/tabContent/tabContent.html | 7 +- .../components/tabContent/tabContent.styl | 6 +- .../worklist/client/components/worklist.html | 4 +- .../worklist/client/components/worklist.js | 11 +-- .../worklist/client/components/worklist.styl | 27 ++------ .../worklistResult/worklistResult.html | 2 +- .../worklistResult/worklistResult.js | 14 ++++ .../worklistResult/worklistResult.styl | 6 ++ .../components/worklistStudy/worklistStudy.js | 1 + Packages/worklist/lib/getStudyMetadata.js | 4 +- Packages/worklist/lib/switchToTab.js | 20 +++++- 43 files changed, 321 insertions(+), 234 deletions(-) create mode 100644 LesionTracker/client/debugReactivity.js delete mode 100644 Packages/lesiontracker/client/collections/PatientLocations.js delete mode 100644 Packages/lesiontracker/client/components/timepointTextDialog/timepointTextDialog.html delete mode 100644 Packages/lesiontracker/client/components/timepointTextDialog/timepointTextDialog.styl diff --git a/LesionTracker/client/components/viewer.html b/LesionTracker/client/components/viewer.html index 2d42d2548..88fd828f3 100644 --- a/LesionTracker/client/components/viewer.html +++ b/LesionTracker/client/components/viewer.html @@ -1,16 +1,18 @@ \ No newline at end of file diff --git a/LesionTracker/client/components/viewer.js b/LesionTracker/client/components/viewer.js index a02ac0127..07b63cb91 100644 --- a/LesionTracker/client/components/viewer.js +++ b/LesionTracker/client/components/viewer.js @@ -1,9 +1,14 @@ Session.setDefault('activeViewport', false); +ViewerStudies = new Meteor.Collection(null); +ViewerStudies._debugName = 'ViewerStudies'; + Template.viewer.onCreated(function() { // Attach the Window resize listener $(window).on('resize', handleResize); + ValidationErrors.remove({}); + var self = this; var firstMeasurementsActivated = false; var contentId = this.data.contentId; @@ -97,10 +102,11 @@ Template.viewer.onCreated(function() { Session.set('activeViewport', ViewerData[contentId].activeViewport || false); // Set lesion tool buttons as disabled if pixel spacing is not available for active element - this.autorun(pixelSpacingAutorunCheck); + self.autorun(pixelSpacingAutorunCheck); // Update the ViewerStudies collection with the loaded studies - ViewerStudies = new Meteor.Collection(null); + ViewerStudies.remove({}); + this.data.studies.forEach(function(study) { study.selected = true; ViewerStudies.insert(study); @@ -110,11 +116,17 @@ Template.viewer.onCreated(function() { Session.set('patientId', patientId); self.autorun(function() { - var patientId = Session.get('patientId'); - self.subscribe('singlePatientTimepoints', patientId); - self.subscribe('singlePatientMeasurements', patientId); + var dataContext = Template.currentData(); + self.subscribe('singlePatientAssociatedStudies', dataContext.studies[0].patientId); + self.subscribe('singlePatientTimepoints', dataContext.studies[0].patientId); + self.subscribe('singlePatientMeasurements', dataContext.studies[0].patientId); + + var subscriptionsReady = self.subscriptionsReady(); + console.log('autorun viewer.js. Ready: ' + subscriptionsReady); + + if (subscriptionsReady) { + TrialResponseCriteria.validateAllDelayed(); - if (self.subscriptionsReady()) { ViewerStudies.find().observe({ added: function(study) { // Find the relevant timepoint given the newly added study @@ -128,7 +140,7 @@ Template.viewer.onCreated(function() { log.warn('Study added to Viewer has not been associated!'); return; } - + // Update the added document with its related timepointId ViewerStudies.update(study._id, { $set: { diff --git a/LesionTracker/client/debugReactivity.js b/LesionTracker/client/debugReactivity.js new file mode 100644 index 000000000..d8878b45d --- /dev/null +++ b/LesionTracker/client/debugReactivity.js @@ -0,0 +1,68 @@ +Meteor.startup(function() { + var debug = false; + + if (debug === true) { + // http://www.meteorpedia.com/read/Debugging_Reactivity + + Meteor.autorun(function(computation) { + computation.onInvalidate(function() { + console.trace(); + }); + }); + + var wrappedFind = Meteor.Collection.prototype.find; + + Meteor.Collection.prototype.find = function() { + var cursor = wrappedFind.apply(this, arguments); + var collectionName = this._name || this._debugName; + + /*cursor.observeChanges({ + added: function(id, fields) { + console.log(collectionName, 'added', id, fields); + }, + changed: function(id, fields) { + console.log(collectionName, 'changed', id, fields); + }, + movedBefore: function(id, before) { + console.log(collectionName, 'movedBefore', id, before); + }, + removed: function(id) { + console.log(collectionName, 'removed', id); + } + });*/ + + cursor.observe({ + added: function(data) { + console.log(collectionName, 'added', data); + }, + changed: function(data) { + console.log(collectionName, 'changed', data); + }, + removed: function(data) { + console.log(collectionName, 'removed', data); + } + }); + + return cursor; + }; + + function logRenders() { + Object.keys(Template).forEach(function(name) { + if (name.indexOf('_') > -1) { + return; + } + + var template = Template[name]; + var oldRender = template.rendered; + var counter = 0; + + template.rendered = function() { + console.log(name, 'render count: ', ++counter); + oldRender && oldRender.apply(this, arguments); + }; + }); + } + + logRenders(); + } +}); diff --git a/Packages/lesiontracker/client/collections/LesionLocations.js b/Packages/lesiontracker/client/collections/LesionLocations.js index d9ab5f75c..4f0c7f528 100644 --- a/Packages/lesiontracker/client/collections/LesionLocations.js +++ b/Packages/lesiontracker/client/collections/LesionLocations.js @@ -1,4 +1,5 @@ LesionLocations = new Meteor.Collection(null); +LesionLocations._debugName = 'LesionLocations'; var organGroups = [ 'Abdominal/Chest Wall', diff --git a/Packages/lesiontracker/client/collections/LocationResponses.js b/Packages/lesiontracker/client/collections/LocationResponses.js index a80a3891e..990adc091 100644 --- a/Packages/lesiontracker/client/collections/LocationResponses.js +++ b/Packages/lesiontracker/client/collections/LocationResponses.js @@ -1,4 +1,5 @@ LocationResponses = new Meteor.Collection(null); +LocationResponses._debugName = 'LocationResponses'; LocationResponses.insert({ text: 'Complete response', diff --git a/Packages/lesiontracker/client/collections/PatientLocations.js b/Packages/lesiontracker/client/collections/PatientLocations.js deleted file mode 100644 index 49283024c..000000000 --- a/Packages/lesiontracker/client/collections/PatientLocations.js +++ /dev/null @@ -1 +0,0 @@ -PatientLocations = new Meteor.Collection(null); \ No newline at end of file diff --git a/Packages/lesiontracker/client/compatibility/lesionTool.js b/Packages/lesiontracker/client/compatibility/lesionTool.js index e5783b660..e602ec443 100644 --- a/Packages/lesiontracker/client/compatibility/lesionTool.js +++ b/Packages/lesiontracker/client/compatibility/lesionTool.js @@ -194,11 +194,10 @@ y: mouseEventData.currentPoints.image.y - 70, pointNearHandle: pointNearTextBox, active: false, - movesIndependently: true, + movesIndependently: false, drawnIndependently: true, allowedOutsideImage: true }, - perpendicularStart: { x: mouseEventData.currentPoints.image.x, y: mouseEventData.currentPoints.image.y, @@ -208,7 +207,6 @@ drawnIndependently: true, index: 2 }, - perpendicularEnd: { x: mouseEventData.currentPoints.image.x, y: mouseEventData.currentPoints.image.y, @@ -217,7 +215,6 @@ drawnIndependently: true, index: 3 } - }, imageId: imageId, seriesInstanceUid: seriesInstanceUid, diff --git a/Packages/lesiontracker/client/compatibility/nonTargetTool.js b/Packages/lesiontracker/client/compatibility/nonTargetTool.js index 939c340c6..b494a1f69 100644 --- a/Packages/lesiontracker/client/compatibility/nonTargetTool.js +++ b/Packages/lesiontracker/client/compatibility/nonTargetTool.js @@ -141,7 +141,7 @@ y: mouseEventData.currentPoints.image.y - 50, pointNearHandle: pointNearTextBox, active: false, - movesIndependently: true, + movesIndependently: false, drawnIndependently: true, allowedOutsideImage: true } diff --git a/Packages/lesiontracker/client/components/associationModal/associationModal.js b/Packages/lesiontracker/client/components/associationModal/associationModal.js index 62d522934..57dbed08f 100644 --- a/Packages/lesiontracker/client/components/associationModal/associationModal.js +++ b/Packages/lesiontracker/client/components/associationModal/associationModal.js @@ -59,18 +59,53 @@ Template.associationModal.events({ // Sort the study dates, so we can get a range for these values studyDates = studyDates.sort(); - // Create a new timepoint to represent the (baseline or follow-up) studies - var timepoint = { - timepointType: timepointType, - timepointId: uuid.new(), - studyInstanceUids: studyInstanceUids, - patientId: relatedStudies[0].patientId, // TODO: Revisit this (Should timepoints be related to patientId?) - earliestDate: studyDates[0].format('YYYYMMDD'), - latestDate: studyDates[studyDates.length - 1].format('YYYYMMDD') - }; + // Check if these studies are already associated with an existing Timepoint + var existingTimepoint; + if (timepointType === 'baseline') { + // If we're trying to associate them to the Baseline, we don't need to + // check if the studyInstanceUids are already associated with anything else + existingTimepoint = Timepoints.findOne({ + patientId: relatedStudies[0].patientId, + timepointType: 'baseline' + }); + } else { + // If we're trying to associate them to a Follow-up, we should check if any + // of them are already part of a Follow-up (e.g. Follow-up 1), so that + // the rest will also be associated with Follow-up 1. + existingTimepoint = Timepoints.findOne({ + patientId: relatedStudies[0].patientId, + studyInstanceUids: { + $in: studyInstanceUids + } + }); + } - // Insert this timepoint into the Timepoints Collection - Timepoints.insert(timepoint); + var timepointId; + if (existingTimepoint) { + // If these studies are already associated with an existing Timepoint, + // and the desired timepoint type is the same (e.g. Follow-up), update + // this Timepoint instead of creating a new one + Timepoints.update(existingTimepoint._id, { + $set: { + studyInstanceUids: studyInstanceUids + } + }); + timepointId = existingTimepoint.timepointId; + } else { + // Create a new timepoint to represent the (baseline or follow-up) studies + var timepoint = { + timepointType: timepointType, + timepointId: uuid.new(), + studyInstanceUids: studyInstanceUids, + patientId: relatedStudies[0].patientId, + earliestDate: studyDates[0].format('YYYYMMDD'), + latestDate: studyDates[studyDates.length - 1].format('YYYYMMDD') + }; + + // Insert this timepoint into the Timepoints Collection + Timepoints.insert(timepoint); + timepointId = timepoint.timepointId; + } // Loop through these studies to associate them with the newly created timepoint relatedStudies.forEach(function(study) { @@ -83,7 +118,7 @@ Template.associationModal.events({ // If a study already exists, update the entry with the new timepointId Studies.update(existingStudy._id, { $set: { - timepointId: timepoint.timepointId + timepointId: timepointId } }); } else { @@ -93,7 +128,7 @@ Template.associationModal.events({ delete study._id; // Attach the timepointId and insert it into the Studies Collection - study.timepointId = timepoint.timepointId; + study.timepointId = timepointId; Studies.insert(study); } }); diff --git a/Packages/lesiontracker/client/components/lesionLocationDialog/lesionLocationDialog.js b/Packages/lesiontracker/client/components/lesionLocationDialog/lesionLocationDialog.js index 972fd6dc3..5a1058972 100644 --- a/Packages/lesiontracker/client/components/lesionLocationDialog/lesionLocationDialog.js +++ b/Packages/lesiontracker/client/components/lesionLocationDialog/lesionLocationDialog.js @@ -205,20 +205,6 @@ Template.lesionLocationDialog.events({ _id: selectedOptionId }); - var id; - var existingLocation = PatientLocations.findOne({ - location: locationObj.location - }); - if (existingLocation) { - id = existingLocation._id; - } else { - // Adds location data to PatientLocation and retrieve the location ID - id = PatientLocations.insert({ - location: locationObj.location, - locationId: locationObj._id - }); - } - // Add an ID value to the tool data to link it to the Measurements collection if (!measurementData.id) { measurementData.id = 'notready'; @@ -226,7 +212,6 @@ Template.lesionLocationDialog.events({ // Link locationUID with active lesion measurementData measurementData.location = locationObj.location; measurementData.locationId = locationObj.id; - measurementData.locationUID = id; /// Set the isTarget value to true, since this is the target-lesion dialog callback measurementData.isTarget = true; @@ -241,8 +226,7 @@ Template.lesionLocationDialog.events({ $set: { location: locationObj.location, locationId: locationObj.id, - isNodal: locationObj.isNodal, - locationUID: id + isNodal: locationObj.isNodal } }); } diff --git a/Packages/lesiontracker/client/components/lesionTrackerWorklistContextMenu/lesionTrackerWorklistContextMenu.js b/Packages/lesiontracker/client/components/lesionTrackerWorklistContextMenu/lesionTrackerWorklistContextMenu.js index a7a6d7b65..c199ce98f 100644 --- a/Packages/lesiontracker/client/components/lesionTrackerWorklistContextMenu/lesionTrackerWorklistContextMenu.js +++ b/Packages/lesiontracker/client/components/lesionTrackerWorklistContextMenu/lesionTrackerWorklistContextMenu.js @@ -31,10 +31,10 @@ function removeTimepointAssociations() { return; } - // Update the Studies Collection to remove the link to this Timepoint - Studies.update(study._id, { - unset: { - timepointId: '' + // Remove this entry from the Studies Collection + Meteor.call('removeAssociatedStudy', study._id, function(error) { + if (error) { + log.warn(error); } }); diff --git a/Packages/lesiontracker/client/components/nonTargetLesionDialog/nonTargetLesionDialog.js b/Packages/lesiontracker/client/components/nonTargetLesionDialog/nonTargetLesionDialog.js index 6de536fd1..6f4c96bb5 100644 --- a/Packages/lesiontracker/client/components/nonTargetLesionDialog/nonTargetLesionDialog.js +++ b/Packages/lesiontracker/client/components/nonTargetLesionDialog/nonTargetLesionDialog.js @@ -70,27 +70,28 @@ function getLesionLocationCallback(measurementData, eventData) { // Find out if this lesion number is already added in the lesion manager for another timepoint // If it is, disable selector location - var locationUID = LesionManager.lesionNumberExists(measurementData); - if (locationUID) { + var locationId = LesionManager.lesionNumberExists(measurementData); + if (locationId) { // Add an ID value to the tool data to link it to the Measurements collection measurementData.id = 'notready'; - measurementData.locationUID = locationUID; + measurementData.locationId = locationId; // Disable the selection of a new location - disableLocationSelection(measurementData.locationUID); + disableLocationSelection(measurementData.locationId); } // Disable selector location to prevent selecting a new location - function disableLocationSelection(locationUID) { - var locationName = LesionManager.getLocationName(locationUID); - selectorLocation.find('option').each(function() { - if ($(this).text() === locationName) { - // Select location in locations dropdown list - selectorLocation.find('option').eq($(this).index()).prop('selected', true); - } - }); - + function disableLocationSelection(locationId) { + var locationObject = LesionLocations.findOne({ + id: locationId + }); + + if (!locationObject) { + return; + } + + selectorLocation.find('option[value="' + locationObject._id + '"]').prop('selected', true); selectorLocation.prop('disabled', true); } @@ -273,26 +274,12 @@ Template.nonTargetLesionDialog.events({ _id: selectedOptionId }); - var id; - var existingLocation = PatientLocations.findOne({ - location: locationObj.location - }); - if (existingLocation) { - id = existingLocation._id; - } else { - // Adds location data to PatientLocation and retrieve the location ID - id = PatientLocations.insert({ - location: locationObj.location - }); - } - if (measurementData.id) { // Update the location data Measurements.update(measurementData.id, { $set: { location: locationObj.location, locationId: locationObj.id, - locationUID: id } }); } else { @@ -300,9 +287,6 @@ Template.nonTargetLesionDialog.events({ measurementData.id = 'notready'; } - // Link locationUID with active lesion measurementData - measurementData.locationUID = id; - /// Set the isTarget value to true, since this is the target-lesion dialog callback measurementData.isTarget = false; diff --git a/Packages/lesiontracker/client/components/studyDateList/studyDateList.js b/Packages/lesiontracker/client/components/studyDateList/studyDateList.js index 30123f62f..9492eec24 100644 --- a/Packages/lesiontracker/client/components/studyDateList/studyDateList.js +++ b/Packages/lesiontracker/client/components/studyDateList/studyDateList.js @@ -76,12 +76,7 @@ Template.studyDateList.events({ var loadingIndicator = selectBox.siblings('.loading'); loadingIndicator.css('display', 'block'); - Meteor.call('GetStudyMetadata', studyInstanceUid, function(error, study) { - if (error) { - log.warn(error); - return; - } - + getStudyMetadata(studyInstanceUid, function(study) { sortStudy(study); // Hide the loading indicator diff --git a/Packages/lesiontracker/client/components/timepointTextDialog/timepointTextDialog.html b/Packages/lesiontracker/client/components/timepointTextDialog/timepointTextDialog.html deleted file mode 100644 index ec2c876a8..000000000 --- a/Packages/lesiontracker/client/components/timepointTextDialog/timepointTextDialog.html +++ /dev/null @@ -1,12 +0,0 @@ - \ No newline at end of file diff --git a/Packages/lesiontracker/client/components/timepointTextDialog/timepointTextDialog.styl b/Packages/lesiontracker/client/components/timepointTextDialog/timepointTextDialog.styl deleted file mode 100644 index 7aa59904a..000000000 --- a/Packages/lesiontracker/client/components/timepointTextDialog/timepointTextDialog.styl +++ /dev/null @@ -1,25 +0,0 @@ -#timepointTextDialog - display: none - position: absolute - z-index: 99 - - .timepointDialogContentWrapper - position: absolute - width: 100px - height: 30px - padding: 5px - background-color: rgba(255,255,255,1) - border-top-left-radius: 5px - border-top-right-radius: 5px - - .dialogContent - margin: 0 auto - float: none - padding: 0 - - .timepointContent - font-size: 16px - - #checkBoxBaseline - height: 16px - width: 16px \ No newline at end of file diff --git a/Packages/lesiontracker/lib/LesionManager.js b/Packages/lesiontracker/lib/LesionManager.js index f8ca7a6c4..737c76bc4 100644 --- a/Packages/lesiontracker/lib/LesionManager.js +++ b/Packages/lesiontracker/lib/LesionManager.js @@ -1,20 +1,3 @@ -/** - * Retrieve a location name (e.g. Liver Right) from the - * PatientLocations Collection by id, if it exists. Otherwise, - * return an empty string. - * - * @param id - * @returns {*|string} - */ -function getLocationName(id) { - var locationObject = PatientLocations.findOne(id); - if (!locationObject || !locationObject.location) { - return ''; - } - - return locationObject.location; -} - /** * Update the Timepoint object for a specific Measurement. * If no measurement exists yet, one will be created. @@ -33,7 +16,6 @@ function updateLesionData(lesionData) { return; } - var timepoint = Timepoints.findOne({ timepointId: study.timepointId }); @@ -84,7 +66,7 @@ function updateLesionData(lesionData) { // Retrieve the location name given the locationUID if (lesionData.locationUID !== undefined) { - var locationObj = PatientLocations.findOne({ + var locationObj = LesionLocations.findOne({ locationUID: lesionData.locationUID }); @@ -191,12 +173,11 @@ function lesionNumberExists(lesionData) { return; } - return measurement.locationUID; + return measurement.locationId; } LesionManager = { updateLesionData: updateLesionData, getNewLesionNumber: getNewLesionNumber, - lesionNumberExists: lesionNumberExists, - getLocationName: getLocationName + lesionNumberExists: lesionNumberExists }; diff --git a/Packages/lesiontracker/lib/TrialCriteriaConstraints.js b/Packages/lesiontracker/lib/TrialCriteriaConstraints.js index b374ccf89..7cbefa342 100644 --- a/Packages/lesiontracker/lib/TrialCriteriaConstraints.js +++ b/Packages/lesiontracker/lib/TrialCriteriaConstraints.js @@ -50,7 +50,7 @@ function RECIST(image) { shortestDiameter: { numericality: { greaterThanOrEqualTo: 15, - message: '^Nodal target lesions must be >= %{count} mm short axis' + //message: '^Nodal target lesions must be >= %{count} mm short axis' } } }; @@ -72,7 +72,7 @@ function RECIST(image) { totalLesionBurden: { numericality: { greaterThanOrEqualTo: 2, // TODO: Check this, the value wasn't specified! - message: '^Total lesion burden (SPD target lesions + SPD new lesions) should be greater than %{count}.' + //message: '^Total lesion burden (SPD target lesions + SPD new lesions) should be greater than %{count}.' } } }; @@ -126,7 +126,7 @@ function RECIST(image) { longestDiameter: { numericality: { greaterThanOrEqualTo: 20, - message: '^Extranodal lesions must be >= %{count} mm on chest X-ray' + //message: '^Extranodal lesions must be >= %{count} mm on chest X-ray' } } }; @@ -135,7 +135,7 @@ function RECIST(image) { longestDiameter: { numericality: { greaterThanOrEqualTo: 10, - message: '^Extranodal target lesions must be >= %{count} mm long axis' + //message: '^Extranodal target lesions must be >= %{count} mm long axis' } } }; @@ -340,6 +340,9 @@ getTrialCriteriaConstraints = function(criteriaType, imageId) { // Otherwise, retrieve the series metaData to identify the modality of the image var seriesMetaData = cornerstoneTools.metaData.get('series', imageId); + if (!seriesMetaData) { + return; + } // TODO: Get the rest of the metaData that has already been loaded by Cornerstone var image = {}; @@ -363,7 +366,6 @@ getTrialCriteriaConstraints = function(criteriaType, imageId) { }); if (!study) { - log.warn('No study/timepoint association.'); return; } diff --git a/Packages/lesiontracker/lib/TrialResponseCriteria.js b/Packages/lesiontracker/lib/TrialResponseCriteria.js index 90cad4c1d..64506deba 100644 --- a/Packages/lesiontracker/lib/TrialResponseCriteria.js +++ b/Packages/lesiontracker/lib/TrialResponseCriteria.js @@ -1,5 +1,6 @@ // Create a client-only Collection to store our Validation Errors ValidationErrors = new Meteor.Collection(null); +ValidationErrors._debugName = 'ValidationErrors'; // Set Validate.js Library's default options validate.options = { @@ -216,8 +217,8 @@ function validateSingleMeasurement(measurementData) { var criteriaType = Session.get('TrialResponseAssessmentCriteria'); var currentConstraints = getTrialCriteriaConstraints(criteriaType, measurementData.imageId); + // If we have no relevant constraints, stop here if (!currentConstraints) { - log.warn('No relevant contraints could be applied'); return; } @@ -236,6 +237,11 @@ function validateSingleMeasurement(measurementData) { // Find the specific measurement data for this Measurement at this Timepoint var currentMeasurement = measurement.timepoints[timepointId]; + // Return here if the measurement was removed during validation + if (!currentMeasurement) { + return; + } + // Include target and nodal flags on the timepoint-specific data so it is easier to validate // TODO: Rethink what to pass to assessSingleMeasurement? currentMeasurement.isTarget = measurement.isTarget; @@ -256,10 +262,11 @@ function validateGroups() { var criteriaType = Session.get('TrialResponseAssessmentCriteria'); Timepoints.find().forEach(function(timepoint) { - // TODO: Criteria for the specific image are retrieved from the general set of criteria. - // - The acquisitionSliceThickness, for example, may be pulled from the image metadata - // - The organ in question, e.g. Chest X-ray, may determine the exact specifications for the current trial criteria + // Criteria for the specific image are retrieved from the general set of criteria. var currentConstraints = getTrialCriteriaConstraints(criteriaType); + if (!currentConstraints) { + return; + } // Retrieve the current constraints which apply to the specific Timepoint type // (e.g. baseline, followup) that this Measurement is being edited on. @@ -289,12 +296,9 @@ function validateAll() { currentMeasurement.lesionNumber = measurement.lesionNumber; currentMeasurement._id = measurement._id; - // TODO: Criteria for the specific image are retrieved from the general set of criteria. - // - The acquisitionSliceThickness, for example, may be pulled from the image metadata - // - The organ in question, e.g. Chest X-ray, may determine the exact specifications for the current trial criteria + // Criteria for the specific image are retrieved from the general set of criteria. var currentConstraints = getTrialCriteriaConstraints(criteriaType, currentMeasurement.imageId); if (!currentConstraints) { - log.warn('No relevant contraints could be applied'); return; } diff --git a/Packages/lesiontracker/lib/activateMeasurements.js b/Packages/lesiontracker/lib/activateMeasurements.js index 59ffebce9..72d917a87 100644 --- a/Packages/lesiontracker/lib/activateMeasurements.js +++ b/Packages/lesiontracker/lib/activateMeasurements.js @@ -12,6 +12,10 @@ activateMeasurements = function(element, measurementId, templateData, viewportIn var timepointData = getTimepointObject(imageId); var measurementData = Measurements.findOne(measurementId); + if (!timepointData) { + return; + } + var measurementAtTimepoint = measurementData.timepoints[timepointData.timepointId]; if (!measurementAtTimepoint) { return; diff --git a/Packages/lesiontracker/lib/clearTools.js b/Packages/lesiontracker/lib/clearTools.js index ac6374569..ea599c115 100644 --- a/Packages/lesiontracker/lib/clearTools.js +++ b/Packages/lesiontracker/lib/clearTools.js @@ -5,11 +5,15 @@ clearTools = function() { var toolStateKeys = Object.keys(toolState).slice(0); var viewportElements = $('.imageViewerViewport').not('.empty'); - var seriesInstanceUIds = []; // Holds seriesInstanceUId of imageViewerViewport elements + var seriesInstanceUids = []; // Holds seriesInstanceUid of imageViewerViewport elements viewportElements.each(function(index, element) { var enabledElement = cornerstone.getEnabledElement(element); var series = cornerstoneTools.metaData.get('series', enabledElement.image.imageId); - seriesInstanceUIds.push(series.seriesInstanceUid); + if (!series) { + return; + } + + seriesInstanceUids.push(series.seriesInstanceUid); }); // Set null array for toolState data found by imageId and toolType @@ -18,8 +22,12 @@ clearTools = function() { var toolTypeData = toolState[imageId][toolType]; if (toolTypeData && toolTypeData.data.length > 0) { var series = cornerstoneTools.metaData.get('series', imageId); - // If seriesInstanceUid is found in seriesInstanceUIds, set toolState data as null - if (seriesInstanceUIds.indexOf(series.seriesInstanceUid) > -1) { + if (!series) { + return; + } + + // If seriesInstanceUid is found in seriesInstanceUids, set toolState data as null + if (seriesInstanceUids.indexOf(series.seriesInstanceUid) > -1) { toolState[imageId][toolType] = { data: [] }; diff --git a/Packages/lesiontracker/lib/handleMeasurementAdded.js b/Packages/lesiontracker/lib/handleMeasurementAdded.js index 2f186be74..b5395ba6f 100644 --- a/Packages/lesiontracker/lib/handleMeasurementAdded.js +++ b/Packages/lesiontracker/lib/handleMeasurementAdded.js @@ -1,10 +1,10 @@ handleMeasurementAdded = function(e, eventData) { - log.info('CornerstoneToolsMeasurementAdded'); var measurementData = eventData.measurementData; switch (eventData.toolType) { case 'nonTarget': case 'lesion': + log.info('CornerstoneToolsMeasurementAdded'); LesionManager.updateLesionData(measurementData); TrialResponseCriteria.validateDelayed(measurementData); break; diff --git a/Packages/lesiontracker/lib/handleMeasurementModified.js b/Packages/lesiontracker/lib/handleMeasurementModified.js index 1fe5685a1..1384c2345 100644 --- a/Packages/lesiontracker/lib/handleMeasurementModified.js +++ b/Packages/lesiontracker/lib/handleMeasurementModified.js @@ -1,10 +1,10 @@ handleMeasurementModified = function(e, eventData) { - log.info('CornerstoneToolsMeasurementModified'); var measurementData = eventData.measurementData; switch (eventData.toolType) { case 'nonTarget': case 'lesion': + log.info('CornerstoneToolsMeasurementModified'); LesionManager.updateLesionData(measurementData); TrialResponseCriteria.validateDelayed(measurementData); break; diff --git a/Packages/lesiontracker/lib/syncMeasurementAndToolData.js b/Packages/lesiontracker/lib/syncMeasurementAndToolData.js index ab4ce0c86..ff57a7297 100644 --- a/Packages/lesiontracker/lib/syncMeasurementAndToolData.js +++ b/Packages/lesiontracker/lib/syncMeasurementAndToolData.js @@ -62,7 +62,7 @@ syncMeasurementAndToolData = function(data) { measurementData.isDeleted = data.isDeleted; measurementData.location = data.location; measurementData.locationUID = data.locationUID; - measurementData.patientId = patientId; + measurementData.patientId = data.patientId; measurementData.visible = data.visible; measurementData.active = data.active; measurementData.uid = data.uid; diff --git a/Packages/lesiontracker/lib/worklist/worklistModification.js b/Packages/lesiontracker/lib/worklist/worklistModification.js index d0b5a2a2a..487a73eef 100644 --- a/Packages/lesiontracker/lib/worklist/worklistModification.js +++ b/Packages/lesiontracker/lib/worklist/worklistModification.js @@ -33,5 +33,8 @@ function dblClickOnStudy(data) { return; } + // Add the Timepoint name to the Patient name to create the tab title + title += ' ' + getTimepointName(timepoint); + openNewTabWithTimepoint(timepoint.timepointId, title); } \ No newline at end of file diff --git a/Packages/lesiontracker/package.js b/Packages/lesiontracker/package.js index 74f8fc106..c2ce367f1 100644 --- a/Packages/lesiontracker/package.js +++ b/Packages/lesiontracker/package.js @@ -29,7 +29,6 @@ Package.onUse(function(api) { // Client-side collections api.addFiles('client/collections/LesionLocations.js', 'client'); api.addFiles('client/collections/LocationResponses.js', 'client'); - api.addFiles('client/collections/PatientLocations.js', 'client'); // Additional Custom Cornerstone Tools for Lesion Tracker api.addFiles('client/compatibility/lesionTool.js', 'client', { @@ -108,9 +107,6 @@ Package.onUse(function(api) { api.addFiles('client/components/nonTargetResponseDialog/nonTargetResponseDialog.styl', 'client'); api.addFiles('client/components/nonTargetResponseDialog/nonTargetResponseDialog.js', 'client'); - api.addFiles('client/components/timepointTextDialog/timepointTextDialog.html', 'client'); - api.addFiles('client/components/timepointTextDialog/timepointTextDialog.styl', 'client'); - api.addFiles('client/components/lesionTrackerWorklistStudy/lesionTrackerWorklistStudy.html', 'client'); api.addFiles('client/components/lesionTrackerWorklistStudy/lesionTrackerWorklistStudy.styl', 'client'); api.addFiles('client/components/lesionTrackerWorklistStudy/lesionTrackerWorklistStudy.js', 'client'); @@ -186,7 +182,6 @@ Package.onUse(function(api) { api.export('ValidationErrors', 'client'); api.export('LesionLocations', 'client'); api.export('LocationResponses', 'client'); - api.export('PatientLocations', 'client'); // Export collections spanning both client and server api.export('Measurements', [ 'client', 'server' ]); diff --git a/Packages/lesiontracker/server/collections.js b/Packages/lesiontracker/server/collections.js index 5faa38ca5..8024a80b0 100644 --- a/Packages/lesiontracker/server/collections.js +++ b/Packages/lesiontracker/server/collections.js @@ -12,6 +12,12 @@ Meteor.publish('studies', function() { return Studies.find(); }); +Meteor.publish('singlePatientAssociatedStudies', function(patientId) { + return Studies.find({ + patientId: patientId + }); +}); + Meteor.publish('singlePatientMeasurements', function(patientId) { return Measurements.find({ patientId: patientId diff --git a/Packages/lesiontracker/server/removeCollections.js b/Packages/lesiontracker/server/removeCollections.js index cc06b52a4..4e06565e6 100644 --- a/Packages/lesiontracker/server/removeCollections.js +++ b/Packages/lesiontracker/server/removeCollections.js @@ -49,5 +49,8 @@ Meteor.methods({ }, removeTimepoint: function(id) { Timepoints.remove(id); + }, + removeAssociatedStudy: function(id) { + Studies.remove(id); } }); diff --git a/Packages/viewerbase/client/components/studyBrowser/imageThumbnail/imageThumbnail.js b/Packages/viewerbase/client/components/studyBrowser/imageThumbnail/imageThumbnail.js index 063821ed1..c1cd5a630 100644 --- a/Packages/viewerbase/client/components/studyBrowser/imageThumbnail/imageThumbnail.js +++ b/Packages/viewerbase/client/components/studyBrowser/imageThumbnail/imageThumbnail.js @@ -28,10 +28,10 @@ Template.imageThumbnail.onRendered(function() { }); Template.imageThumbnail.helpers({ - 'percentComplete': function() { + percentComplete: function() { var percentComplete = Session.get('CornerstoneThumbnailLoadProgress' + this.thumbnailIndex); if (percentComplete && percentComplete !== 100) { return percentComplete + '%'; } } -}); \ No newline at end of file +}); diff --git a/Packages/viewerbase/client/components/viewer/hangingProtocolButtons/hangingProtocolButtons.js b/Packages/viewerbase/client/components/viewer/hangingProtocolButtons/hangingProtocolButtons.js index f306d162d..7bdeff472 100644 --- a/Packages/viewerbase/client/components/viewer/hangingProtocolButtons/hangingProtocolButtons.js +++ b/Packages/viewerbase/client/components/viewer/hangingProtocolButtons/hangingProtocolButtons.js @@ -1,11 +1,11 @@ Template.hangingProtocolButtons.helpers({ - 'isNextAvailable': function() { + isNextAvailable: function() { var presentationGroup = Session.get('WindowManagerPresentationGroup'); var numPresentationGroups = WindowManager.getNumPresentationGroups(); return presentationGroup < numPresentationGroups; }, - 'isPreviousAvailable': function() { + isPreviousAvailable: function() { var presentationGroup = Session.get('WindowManagerPresentationGroup'); return presentationGroup > 1; } -}); \ No newline at end of file +}); diff --git a/Packages/viewerbase/client/components/viewer/imageViewerViewport/imageViewerViewport.js b/Packages/viewerbase/client/components/viewer/imageViewerViewport/imageViewerViewport.js index be1c8d891..8f78a6a9c 100644 --- a/Packages/viewerbase/client/components/viewer/imageViewerViewport/imageViewerViewport.js +++ b/Packages/viewerbase/client/components/viewer/imageViewerViewport/imageViewerViewport.js @@ -411,6 +411,10 @@ Meteor.startup(function() { cornerstoneTools.magnify.setConfiguration(config); }); +Template.imageViewerViewport.onCreated(function() { + console.log('imageViewerViewport onCreated'); +}); + Template.imageViewerViewport.onRendered(function() { var templateData = Template.currentData(); log.info('imageViewerViewport onRendered'); @@ -455,7 +459,7 @@ Template.imageViewerViewport.onRendered(function() { // TODO: This code block might be refactored // Load previous measurement study when reloading a patient if (!study) { - Meteor.call('GetStudyMetadata', this.data.studyInstanceUid, function(error, study) { + getStudyMetadata(this.data.studyInstanceUid, function(study) { // Once we have retrieved the data, we sort the series' by series // and instance number in ascending order if (!study) { @@ -466,7 +470,7 @@ Template.imageViewerViewport.onRendered(function() { data.study = study; setSeries(data, seriesInstanceUid, templateData); - }); + }) } data.study = study; @@ -482,7 +486,10 @@ Template.imageViewerViewport.onDestroyed(function() { // Try to stop any currently playing clips // Otherwise the interval will continuously throw errors try { - cornerstoneTools.stopClip(element); + var enabledElement = cornerstone.getEnabledElement(element); + if (enabledElement) { + cornerstoneTools.stopClip(element); + } } catch (error) { log.warn(error); } diff --git a/Packages/viewerbase/client/components/viewer/imageViewerViewports/imageViewerViewports.js b/Packages/viewerbase/client/components/viewer/imageViewerViewports/imageViewerViewports.js index 96ce8ae14..c9d175031 100644 --- a/Packages/viewerbase/client/components/viewer/imageViewerViewports/imageViewerViewports.js +++ b/Packages/viewerbase/client/components/viewer/imageViewerViewports/imageViewerViewports.js @@ -1,4 +1,5 @@ ViewerWindows = new Meteor.Collection(null); +ViewerWindows._debugName = 'ViewerWindows'; Template.imageViewerViewports.helpers({ height: function() { @@ -10,14 +11,16 @@ Template.imageViewerViewports.helpers({ return 100 / viewportColumns; }, viewerWindow: function() { - ViewerWindows = new Meteor.Collection(null); + log.info('ViewerWindows'); + //log.info(ViewerWindows.find().fetch()); + ViewerWindows.remove({}); - log.info("imageViewerViewports viewportArray"); + log.info('imageViewerViewports viewportArray'); var viewportRows = this.viewportRows || 1; var viewportColumns = this.viewportColumns || 1; - var contentId = this.contentId || $("#viewer").parents(".tab-pane.active").attr('id'); + var contentId = this.contentId || $('#viewer').parents('.tab-pane.active').attr('id'); if (this.viewportRows && this.viewportColumns) { viewportRows = this.viewportRows || 1; viewportColumns = this.viewportColumns || 1; @@ -47,13 +50,13 @@ Template.imageViewerViewports.helpers({ // Update viewerData ViewerData[contentId].viewportRows = viewportRows; ViewerData[contentId].viewportColumns = viewportColumns; - Session.set("ViewerData", ViewerData); + Session.set('ViewerData', ViewerData); this.viewportRows = viewportRows; this.viewportColumns = viewportColumns; var numViewports = viewportRows * viewportColumns; - for (var i=0; i < numViewports; ++i) { + for (var i = 0; i < numViewports; ++i) { var data = { viewportIndex: i, // These two are necessary because otherwise the width and height helpers @@ -77,14 +80,17 @@ Template.imageViewerViewports.helpers({ ViewerWindows.insert(data); } - // Here we will find out if we need to load any other studies into the viewer // We will make a list of unique studyInstanceUids var uniqueStudyInstanceUids = []; // Meteor doesn't support Mongo's 'distinct' function, so we have to do this in a loop - ViewerWindows.find().forEach(function(window) { + var windows = ViewerWindows.find({}, { + reactive: false + }).fetch(); + + windows.forEach(function(window) { var studyInstanceUid = window.studyInstanceUid; if (!studyInstanceUid) { return; @@ -99,10 +105,17 @@ Template.imageViewerViewports.helpers({ uniqueStudyInstanceUids.push(studyInstanceUid); // If any of the associated studies is not already loaded, load it now - var loadedStudy = ViewerStudies.findOne({studyInstanceUid: studyInstanceUid}); + var loadedStudy = ViewerStudies.findOne({ + studyInstanceUid: studyInstanceUid + }, { + reactive: false + }); + if (!loadedStudy) { // Load the study - Meteor.call('GetStudyMetadata', studyInstanceUid, function(error, study) { + getStudyMetadata(studyInstanceUid, function(study) { + log.info("imageViewerViewports GetStudyMetadata: " + studyInstanceUid); + // Sort the study's series and instances by series and instance number sortStudy(study); @@ -112,7 +125,9 @@ Template.imageViewerViewports.helpers({ } }); - return ViewerWindows.find(); + return ViewerWindows.find({}, { + reactive: false + }).fetch(); } }); @@ -122,9 +137,9 @@ var savedSeriesData, Template.imageViewerViewports.events({ 'CornerstoneMouseDoubleClick .imageViewerViewport': function(e) { - var container = $(".viewerMain").get(0); + var container = $('.viewerMain').get(0); var data; - var contentId = this.contentId || $("#viewer").parents(".tab-pane.active").attr('id'); + var contentId = this.contentId || $('#viewer').parents('.tab-pane.active').attr('id'); // If there is more than one viewport on screen // And one of them is double-clicked, it should be rendered alone @@ -183,4 +198,4 @@ Template.imageViewerViewports.events({ $('.imageViewerViewport').eq(0).addClass('zoomed'); } } -}); \ No newline at end of file +}); diff --git a/Packages/viewerbase/server/services/dimse/instances.js b/Packages/viewerbase/server/services/dimse/instances.js index b18e72e61..fa2d414e0 100755 --- a/Packages/viewerbase/server/services/dimse/instances.js +++ b/Packages/viewerbase/server/services/dimse/instances.js @@ -59,7 +59,7 @@ function resultDataToStudyMetadata(resultData) { */ Services.DIMSE.Instances = function(studyInstanceUid) { //var url = buildUrl(server, studyInstanceUid); - var result = DIMSE.retrieveInstances(studyInstanceUid); + var result = DIMSE.retrieveInstances(studyInstanceUid, null, {0x00080018 : ""}); console.log("DIMSE Instance retrieval"); console.log(result); diff --git a/Packages/worklist/client/components/tabContent/tabContent.html b/Packages/worklist/client/components/tabContent/tabContent.html index 390d5f74a..5d1eb1246 100644 --- a/Packages/worklist/client/components/tabContent/tabContent.html +++ b/Packages/worklist/client/components/tabContent/tabContent.html @@ -1,5 +1,10 @@ \ No newline at end of file diff --git a/Packages/worklist/client/components/tabContent/tabContent.styl b/Packages/worklist/client/components/tabContent/tabContent.styl index f7820215b..3fbf9d528 100644 --- a/Packages/worklist/client/components/tabContent/tabContent.styl +++ b/Packages/worklist/client/components/tabContent/tabContent.styl @@ -1,4 +1,8 @@ .tab-pane .loadingTextDiv h5 - color: #777 \ No newline at end of file + color: #777 + + .viewerContainer + height: 100% + width: 100% \ No newline at end of file diff --git a/Packages/worklist/client/components/worklist.html b/Packages/worklist/client/components/worklist.html index 09c8ae6a1..775973943 100644 --- a/Packages/worklist/client/components/worklist.html +++ b/Packages/worklist/client/components/worklist.html @@ -7,9 +7,9 @@ {{ >tabTitle }} {{ /each }} -
+
-
+
{{> worklistResult }}
diff --git a/Packages/worklist/client/components/worklist.js b/Packages/worklist/client/components/worklist.js index 0506a62bc..3673f381f 100644 --- a/Packages/worklist/client/components/worklist.js +++ b/Packages/worklist/client/components/worklist.js @@ -11,20 +11,13 @@ ViewerData = Session.get('ViewerData') || {}; // Create the WorklistTabs collection WorklistTabs = new Meteor.Collection(null); +WorklistTabs._debugName = 'WorklistTabs'; // Define the WorklistStudies Collection // This is a client-side only Collection which // Stores the list of studies in the Worklist WorklistStudies = new Meteor.Collection(null); - -Template.worklist.onCreated(function() { - var self = this; - if (Worklist.subscriptions) { - Worklist.subscriptions.forEach(function(collectionName) { - self.subscribe(collectionName); - }); - } -}); +WorklistStudies._debugName = 'WorklistStudies'; Template.worklist.onRendered(function() { // If there is a tab set as active in the Session, diff --git a/Packages/worklist/client/components/worklist.styl b/Packages/worklist/client/components/worklist.styl index 0b98edb27..0253870fb 100644 --- a/Packages/worklist/client/components/worklist.styl +++ b/Packages/worklist/client/components/worklist.styl @@ -9,37 +9,18 @@ body -ms-user-select: none user-select: none -input.worklist-search - height: 25px - background-color: #888888 - width: 100% - #tblStudyList tr height: 20px -.patient-name-input - width: 100% - -.worklist-input - width: 100% - -.modality-input - width: 100% - -.patientid-input - width: 100% - -.study-description-input - width: 100% - #worklistTab background-color: #202020 -#worklistContainer +.worklistContainer background-color: #202020 + margin: 0 auto color: white - width:90% + width: 90% padding-top: 10px #tabs @@ -99,7 +80,7 @@ input.worklist-search .tab-content width: 100% height: calc(100% - 91px) - + .tab-pane width: 100% height: 100% \ No newline at end of file diff --git a/Packages/worklist/client/components/worklistResult/worklistResult.html b/Packages/worklist/client/components/worklistResult/worklistResult.html index 8dc51a4f1..8d8842074 100644 --- a/Packages/worklist/client/components/worklistResult/worklistResult.html +++ b/Packages/worklist/client/components/worklistResult/worklistResult.html @@ -1,5 +1,5 @@