Remove some subscription / reactivity / caching bugs and the unnecessary PatientLocations collection
This commit is contained in:
parent
7d42456f57
commit
9cde04bcfe
@ -1,16 +1,18 @@
|
|||||||
<template name="viewer">
|
<template name="viewer">
|
||||||
|
{{#if Template.subscriptionsReady}}
|
||||||
<div id="viewer">
|
<div id="viewer">
|
||||||
{{>confirmDeleteDialog}}
|
{{>confirmDeleteDialog}}
|
||||||
{{>lesionLocationDialog}}
|
{{>lesionLocationDialog}}
|
||||||
{{>nonTargetLesionDialog}}
|
{{>nonTargetLesionDialog}}
|
||||||
{{>nonTargetResponseDialog}}
|
{{>nonTargetResponseDialog}}
|
||||||
{{>timepointTextDialog}}
|
{{>conformanceCheckFeedback}}
|
||||||
{{ >conformanceCheckFeedback }}
|
|
||||||
|
|
||||||
{{>hidingPanel}}
|
{{>hidingPanel}}
|
||||||
<div id="viewportAndLesionTable">
|
<div id="viewportAndLesionTable">
|
||||||
{{>viewerMain }}
|
{{>viewerMain }}
|
||||||
{{>lesionTable }}
|
{{>lesionTable }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{{else}}
|
||||||
|
{{>loadingText}}
|
||||||
|
{{/if}}
|
||||||
</template>
|
</template>
|
||||||
@ -1,9 +1,14 @@
|
|||||||
Session.setDefault('activeViewport', false);
|
Session.setDefault('activeViewport', false);
|
||||||
|
|
||||||
|
ViewerStudies = new Meteor.Collection(null);
|
||||||
|
ViewerStudies._debugName = 'ViewerStudies';
|
||||||
|
|
||||||
Template.viewer.onCreated(function() {
|
Template.viewer.onCreated(function() {
|
||||||
// Attach the Window resize listener
|
// Attach the Window resize listener
|
||||||
$(window).on('resize', handleResize);
|
$(window).on('resize', handleResize);
|
||||||
|
|
||||||
|
ValidationErrors.remove({});
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
var firstMeasurementsActivated = false;
|
var firstMeasurementsActivated = false;
|
||||||
var contentId = this.data.contentId;
|
var contentId = this.data.contentId;
|
||||||
@ -97,10 +102,11 @@ Template.viewer.onCreated(function() {
|
|||||||
Session.set('activeViewport', ViewerData[contentId].activeViewport || false);
|
Session.set('activeViewport', ViewerData[contentId].activeViewport || false);
|
||||||
|
|
||||||
// Set lesion tool buttons as disabled if pixel spacing is not available for active element
|
// 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
|
// Update the ViewerStudies collection with the loaded studies
|
||||||
ViewerStudies = new Meteor.Collection(null);
|
ViewerStudies.remove({});
|
||||||
|
|
||||||
this.data.studies.forEach(function(study) {
|
this.data.studies.forEach(function(study) {
|
||||||
study.selected = true;
|
study.selected = true;
|
||||||
ViewerStudies.insert(study);
|
ViewerStudies.insert(study);
|
||||||
@ -110,11 +116,17 @@ Template.viewer.onCreated(function() {
|
|||||||
Session.set('patientId', patientId);
|
Session.set('patientId', patientId);
|
||||||
|
|
||||||
self.autorun(function() {
|
self.autorun(function() {
|
||||||
var patientId = Session.get('patientId');
|
var dataContext = Template.currentData();
|
||||||
self.subscribe('singlePatientTimepoints', patientId);
|
self.subscribe('singlePatientAssociatedStudies', dataContext.studies[0].patientId);
|
||||||
self.subscribe('singlePatientMeasurements', 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({
|
ViewerStudies.find().observe({
|
||||||
added: function(study) {
|
added: function(study) {
|
||||||
// Find the relevant timepoint given the newly added study
|
// Find the relevant timepoint given the newly added study
|
||||||
|
|||||||
68
LesionTracker/client/debugReactivity.js
Normal file
68
LesionTracker/client/debugReactivity.js
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -1,4 +1,5 @@
|
|||||||
LesionLocations = new Meteor.Collection(null);
|
LesionLocations = new Meteor.Collection(null);
|
||||||
|
LesionLocations._debugName = 'LesionLocations';
|
||||||
|
|
||||||
var organGroups = [
|
var organGroups = [
|
||||||
'Abdominal/Chest Wall',
|
'Abdominal/Chest Wall',
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
LocationResponses = new Meteor.Collection(null);
|
LocationResponses = new Meteor.Collection(null);
|
||||||
|
LocationResponses._debugName = 'LocationResponses';
|
||||||
|
|
||||||
LocationResponses.insert({
|
LocationResponses.insert({
|
||||||
text: 'Complete response',
|
text: 'Complete response',
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
PatientLocations = new Meteor.Collection(null);
|
|
||||||
@ -194,11 +194,10 @@
|
|||||||
y: mouseEventData.currentPoints.image.y - 70,
|
y: mouseEventData.currentPoints.image.y - 70,
|
||||||
pointNearHandle: pointNearTextBox,
|
pointNearHandle: pointNearTextBox,
|
||||||
active: false,
|
active: false,
|
||||||
movesIndependently: true,
|
movesIndependently: false,
|
||||||
drawnIndependently: true,
|
drawnIndependently: true,
|
||||||
allowedOutsideImage: true
|
allowedOutsideImage: true
|
||||||
},
|
},
|
||||||
|
|
||||||
perpendicularStart: {
|
perpendicularStart: {
|
||||||
x: mouseEventData.currentPoints.image.x,
|
x: mouseEventData.currentPoints.image.x,
|
||||||
y: mouseEventData.currentPoints.image.y,
|
y: mouseEventData.currentPoints.image.y,
|
||||||
@ -208,7 +207,6 @@
|
|||||||
drawnIndependently: true,
|
drawnIndependently: true,
|
||||||
index: 2
|
index: 2
|
||||||
},
|
},
|
||||||
|
|
||||||
perpendicularEnd: {
|
perpendicularEnd: {
|
||||||
x: mouseEventData.currentPoints.image.x,
|
x: mouseEventData.currentPoints.image.x,
|
||||||
y: mouseEventData.currentPoints.image.y,
|
y: mouseEventData.currentPoints.image.y,
|
||||||
@ -217,7 +215,6 @@
|
|||||||
drawnIndependently: true,
|
drawnIndependently: true,
|
||||||
index: 3
|
index: 3
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
imageId: imageId,
|
imageId: imageId,
|
||||||
seriesInstanceUid: seriesInstanceUid,
|
seriesInstanceUid: seriesInstanceUid,
|
||||||
|
|||||||
@ -141,7 +141,7 @@
|
|||||||
y: mouseEventData.currentPoints.image.y - 50,
|
y: mouseEventData.currentPoints.image.y - 50,
|
||||||
pointNearHandle: pointNearTextBox,
|
pointNearHandle: pointNearTextBox,
|
||||||
active: false,
|
active: false,
|
||||||
movesIndependently: true,
|
movesIndependently: false,
|
||||||
drawnIndependently: true,
|
drawnIndependently: true,
|
||||||
allowedOutsideImage: true
|
allowedOutsideImage: true
|
||||||
}
|
}
|
||||||
|
|||||||
@ -59,18 +59,53 @@ Template.associationModal.events({
|
|||||||
// Sort the study dates, so we can get a range for these values
|
// Sort the study dates, so we can get a range for these values
|
||||||
studyDates = studyDates.sort();
|
studyDates = studyDates.sort();
|
||||||
|
|
||||||
// Create a new timepoint to represent the (baseline or follow-up) studies
|
// Check if these studies are already associated with an existing Timepoint
|
||||||
var timepoint = {
|
var existingTimepoint;
|
||||||
timepointType: timepointType,
|
if (timepointType === 'baseline') {
|
||||||
timepointId: uuid.new(),
|
// If we're trying to associate them to the Baseline, we don't need to
|
||||||
studyInstanceUids: studyInstanceUids,
|
// check if the studyInstanceUids are already associated with anything else
|
||||||
patientId: relatedStudies[0].patientId, // TODO: Revisit this (Should timepoints be related to patientId?)
|
existingTimepoint = Timepoints.findOne({
|
||||||
earliestDate: studyDates[0].format('YYYYMMDD'),
|
patientId: relatedStudies[0].patientId,
|
||||||
latestDate: studyDates[studyDates.length - 1].format('YYYYMMDD')
|
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
|
var timepointId;
|
||||||
Timepoints.insert(timepoint);
|
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
|
// Loop through these studies to associate them with the newly created timepoint
|
||||||
relatedStudies.forEach(function(study) {
|
relatedStudies.forEach(function(study) {
|
||||||
@ -83,7 +118,7 @@ Template.associationModal.events({
|
|||||||
// If a study already exists, update the entry with the new timepointId
|
// If a study already exists, update the entry with the new timepointId
|
||||||
Studies.update(existingStudy._id, {
|
Studies.update(existingStudy._id, {
|
||||||
$set: {
|
$set: {
|
||||||
timepointId: timepoint.timepointId
|
timepointId: timepointId
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -93,7 +128,7 @@ Template.associationModal.events({
|
|||||||
delete study._id;
|
delete study._id;
|
||||||
|
|
||||||
// Attach the timepointId and insert it into the Studies Collection
|
// Attach the timepointId and insert it into the Studies Collection
|
||||||
study.timepointId = timepoint.timepointId;
|
study.timepointId = timepointId;
|
||||||
Studies.insert(study);
|
Studies.insert(study);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -205,20 +205,6 @@ Template.lesionLocationDialog.events({
|
|||||||
_id: selectedOptionId
|
_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
|
// Add an ID value to the tool data to link it to the Measurements collection
|
||||||
if (!measurementData.id) {
|
if (!measurementData.id) {
|
||||||
measurementData.id = 'notready';
|
measurementData.id = 'notready';
|
||||||
@ -226,7 +212,6 @@ Template.lesionLocationDialog.events({
|
|||||||
// Link locationUID with active lesion measurementData
|
// Link locationUID with active lesion measurementData
|
||||||
measurementData.location = locationObj.location;
|
measurementData.location = locationObj.location;
|
||||||
measurementData.locationId = locationObj.id;
|
measurementData.locationId = locationObj.id;
|
||||||
measurementData.locationUID = id;
|
|
||||||
|
|
||||||
/// Set the isTarget value to true, since this is the target-lesion dialog callback
|
/// Set the isTarget value to true, since this is the target-lesion dialog callback
|
||||||
measurementData.isTarget = true;
|
measurementData.isTarget = true;
|
||||||
@ -241,8 +226,7 @@ Template.lesionLocationDialog.events({
|
|||||||
$set: {
|
$set: {
|
||||||
location: locationObj.location,
|
location: locationObj.location,
|
||||||
locationId: locationObj.id,
|
locationId: locationObj.id,
|
||||||
isNodal: locationObj.isNodal,
|
isNodal: locationObj.isNodal
|
||||||
locationUID: id
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,10 +31,10 @@ function removeTimepointAssociations() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the Studies Collection to remove the link to this Timepoint
|
// Remove this entry from the Studies Collection
|
||||||
Studies.update(study._id, {
|
Meteor.call('removeAssociatedStudy', study._id, function(error) {
|
||||||
unset: {
|
if (error) {
|
||||||
timepointId: ''
|
log.warn(error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -70,27 +70,28 @@ function getLesionLocationCallback(measurementData, eventData) {
|
|||||||
|
|
||||||
// Find out if this lesion number is already added in the lesion manager for another timepoint
|
// Find out if this lesion number is already added in the lesion manager for another timepoint
|
||||||
// If it is, disable selector location
|
// If it is, disable selector location
|
||||||
var locationUID = LesionManager.lesionNumberExists(measurementData);
|
var locationId = LesionManager.lesionNumberExists(measurementData);
|
||||||
if (locationUID) {
|
if (locationId) {
|
||||||
// Add an ID value to the tool data to link it to the Measurements collection
|
// Add an ID value to the tool data to link it to the Measurements collection
|
||||||
measurementData.id = 'notready';
|
measurementData.id = 'notready';
|
||||||
|
|
||||||
measurementData.locationUID = locationUID;
|
measurementData.locationId = locationId;
|
||||||
|
|
||||||
// Disable the selection of a new location
|
// Disable the selection of a new location
|
||||||
disableLocationSelection(measurementData.locationUID);
|
disableLocationSelection(measurementData.locationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable selector location to prevent selecting a new location
|
// Disable selector location to prevent selecting a new location
|
||||||
function disableLocationSelection(locationUID) {
|
function disableLocationSelection(locationId) {
|
||||||
var locationName = LesionManager.getLocationName(locationUID);
|
var locationObject = LesionLocations.findOne({
|
||||||
selectorLocation.find('option').each(function() {
|
id: locationId
|
||||||
if ($(this).text() === locationName) {
|
});
|
||||||
// Select location in locations dropdown list
|
|
||||||
selectorLocation.find('option').eq($(this).index()).prop('selected', true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
if (!locationObject) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectorLocation.find('option[value="' + locationObject._id + '"]').prop('selected', true);
|
||||||
selectorLocation.prop('disabled', true);
|
selectorLocation.prop('disabled', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,26 +274,12 @@ Template.nonTargetLesionDialog.events({
|
|||||||
_id: selectedOptionId
|
_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) {
|
if (measurementData.id) {
|
||||||
// Update the location data
|
// Update the location data
|
||||||
Measurements.update(measurementData.id, {
|
Measurements.update(measurementData.id, {
|
||||||
$set: {
|
$set: {
|
||||||
location: locationObj.location,
|
location: locationObj.location,
|
||||||
locationId: locationObj.id,
|
locationId: locationObj.id,
|
||||||
locationUID: id
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -300,9 +287,6 @@ Template.nonTargetLesionDialog.events({
|
|||||||
measurementData.id = 'notready';
|
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
|
/// Set the isTarget value to true, since this is the target-lesion dialog callback
|
||||||
measurementData.isTarget = false;
|
measurementData.isTarget = false;
|
||||||
|
|
||||||
|
|||||||
@ -76,12 +76,7 @@ Template.studyDateList.events({
|
|||||||
var loadingIndicator = selectBox.siblings('.loading');
|
var loadingIndicator = selectBox.siblings('.loading');
|
||||||
loadingIndicator.css('display', 'block');
|
loadingIndicator.css('display', 'block');
|
||||||
|
|
||||||
Meteor.call('GetStudyMetadata', studyInstanceUid, function(error, study) {
|
getStudyMetadata(studyInstanceUid, function(study) {
|
||||||
if (error) {
|
|
||||||
log.warn(error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sortStudy(study);
|
sortStudy(study);
|
||||||
|
|
||||||
// Hide the loading indicator
|
// Hide the loading indicator
|
||||||
|
|||||||
@ -1,12 +0,0 @@
|
|||||||
<template name="timepointTextDialog">
|
|
||||||
<div id="timepointTextDialog">
|
|
||||||
<div class="timepointDialogContentWrapper">
|
|
||||||
<div class="dialogContent">
|
|
||||||
<div class="timepointContent">
|
|
||||||
<input type="checkbox" id="checkBoxBaseline" value="ok">
|
|
||||||
<label for="checkBoxBaseline">Baseline</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@ -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
|
|
||||||
@ -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.
|
* Update the Timepoint object for a specific Measurement.
|
||||||
* If no measurement exists yet, one will be created.
|
* If no measurement exists yet, one will be created.
|
||||||
@ -33,7 +16,6 @@ function updateLesionData(lesionData) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var timepoint = Timepoints.findOne({
|
var timepoint = Timepoints.findOne({
|
||||||
timepointId: study.timepointId
|
timepointId: study.timepointId
|
||||||
});
|
});
|
||||||
@ -84,7 +66,7 @@ function updateLesionData(lesionData) {
|
|||||||
|
|
||||||
// Retrieve the location name given the locationUID
|
// Retrieve the location name given the locationUID
|
||||||
if (lesionData.locationUID !== undefined) {
|
if (lesionData.locationUID !== undefined) {
|
||||||
var locationObj = PatientLocations.findOne({
|
var locationObj = LesionLocations.findOne({
|
||||||
locationUID: lesionData.locationUID
|
locationUID: lesionData.locationUID
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -191,12 +173,11 @@ function lesionNumberExists(lesionData) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return measurement.locationUID;
|
return measurement.locationId;
|
||||||
}
|
}
|
||||||
|
|
||||||
LesionManager = {
|
LesionManager = {
|
||||||
updateLesionData: updateLesionData,
|
updateLesionData: updateLesionData,
|
||||||
getNewLesionNumber: getNewLesionNumber,
|
getNewLesionNumber: getNewLesionNumber,
|
||||||
lesionNumberExists: lesionNumberExists,
|
lesionNumberExists: lesionNumberExists
|
||||||
getLocationName: getLocationName
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -50,7 +50,7 @@ function RECIST(image) {
|
|||||||
shortestDiameter: {
|
shortestDiameter: {
|
||||||
numericality: {
|
numericality: {
|
||||||
greaterThanOrEqualTo: 15,
|
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: {
|
totalLesionBurden: {
|
||||||
numericality: {
|
numericality: {
|
||||||
greaterThanOrEqualTo: 2, // TODO: Check this, the value wasn't specified!
|
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: {
|
longestDiameter: {
|
||||||
numericality: {
|
numericality: {
|
||||||
greaterThanOrEqualTo: 20,
|
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: {
|
longestDiameter: {
|
||||||
numericality: {
|
numericality: {
|
||||||
greaterThanOrEqualTo: 10,
|
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
|
// Otherwise, retrieve the series metaData to identify the modality of the image
|
||||||
var seriesMetaData = cornerstoneTools.metaData.get('series', imageId);
|
var seriesMetaData = cornerstoneTools.metaData.get('series', imageId);
|
||||||
|
if (!seriesMetaData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Get the rest of the metaData that has already been loaded by Cornerstone
|
// TODO: Get the rest of the metaData that has already been loaded by Cornerstone
|
||||||
var image = {};
|
var image = {};
|
||||||
@ -363,7 +366,6 @@ getTrialCriteriaConstraints = function(criteriaType, imageId) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!study) {
|
if (!study) {
|
||||||
log.warn('No study/timepoint association.');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
// Create a client-only Collection to store our Validation Errors
|
// Create a client-only Collection to store our Validation Errors
|
||||||
ValidationErrors = new Meteor.Collection(null);
|
ValidationErrors = new Meteor.Collection(null);
|
||||||
|
ValidationErrors._debugName = 'ValidationErrors';
|
||||||
|
|
||||||
// Set Validate.js Library's default options
|
// Set Validate.js Library's default options
|
||||||
validate.options = {
|
validate.options = {
|
||||||
@ -216,8 +217,8 @@ function validateSingleMeasurement(measurementData) {
|
|||||||
var criteriaType = Session.get('TrialResponseAssessmentCriteria');
|
var criteriaType = Session.get('TrialResponseAssessmentCriteria');
|
||||||
var currentConstraints = getTrialCriteriaConstraints(criteriaType, measurementData.imageId);
|
var currentConstraints = getTrialCriteriaConstraints(criteriaType, measurementData.imageId);
|
||||||
|
|
||||||
|
// If we have no relevant constraints, stop here
|
||||||
if (!currentConstraints) {
|
if (!currentConstraints) {
|
||||||
log.warn('No relevant contraints could be applied');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,6 +237,11 @@ function validateSingleMeasurement(measurementData) {
|
|||||||
// Find the specific measurement data for this Measurement at this Timepoint
|
// Find the specific measurement data for this Measurement at this Timepoint
|
||||||
var currentMeasurement = measurement.timepoints[timepointId];
|
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
|
// Include target and nodal flags on the timepoint-specific data so it is easier to validate
|
||||||
// TODO: Rethink what to pass to assessSingleMeasurement?
|
// TODO: Rethink what to pass to assessSingleMeasurement?
|
||||||
currentMeasurement.isTarget = measurement.isTarget;
|
currentMeasurement.isTarget = measurement.isTarget;
|
||||||
@ -256,10 +262,11 @@ function validateGroups() {
|
|||||||
var criteriaType = Session.get('TrialResponseAssessmentCriteria');
|
var criteriaType = Session.get('TrialResponseAssessmentCriteria');
|
||||||
|
|
||||||
Timepoints.find().forEach(function(timepoint) {
|
Timepoints.find().forEach(function(timepoint) {
|
||||||
// TODO: Criteria for the specific image are retrieved from the general set of criteria.
|
// 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
|
|
||||||
var currentConstraints = getTrialCriteriaConstraints(criteriaType);
|
var currentConstraints = getTrialCriteriaConstraints(criteriaType);
|
||||||
|
if (!currentConstraints) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Retrieve the current constraints which apply to the specific Timepoint type
|
// Retrieve the current constraints which apply to the specific Timepoint type
|
||||||
// (e.g. baseline, followup) that this Measurement is being edited on.
|
// (e.g. baseline, followup) that this Measurement is being edited on.
|
||||||
@ -289,12 +296,9 @@ function validateAll() {
|
|||||||
currentMeasurement.lesionNumber = measurement.lesionNumber;
|
currentMeasurement.lesionNumber = measurement.lesionNumber;
|
||||||
currentMeasurement._id = measurement._id;
|
currentMeasurement._id = measurement._id;
|
||||||
|
|
||||||
// TODO: Criteria for the specific image are retrieved from the general set of criteria.
|
// 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
|
|
||||||
var currentConstraints = getTrialCriteriaConstraints(criteriaType, currentMeasurement.imageId);
|
var currentConstraints = getTrialCriteriaConstraints(criteriaType, currentMeasurement.imageId);
|
||||||
if (!currentConstraints) {
|
if (!currentConstraints) {
|
||||||
log.warn('No relevant contraints could be applied');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -12,6 +12,10 @@ activateMeasurements = function(element, measurementId, templateData, viewportIn
|
|||||||
var timepointData = getTimepointObject(imageId);
|
var timepointData = getTimepointObject(imageId);
|
||||||
var measurementData = Measurements.findOne(measurementId);
|
var measurementData = Measurements.findOne(measurementId);
|
||||||
|
|
||||||
|
if (!timepointData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var measurementAtTimepoint = measurementData.timepoints[timepointData.timepointId];
|
var measurementAtTimepoint = measurementData.timepoints[timepointData.timepointId];
|
||||||
if (!measurementAtTimepoint) {
|
if (!measurementAtTimepoint) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -5,11 +5,15 @@ clearTools = function() {
|
|||||||
var toolStateKeys = Object.keys(toolState).slice(0);
|
var toolStateKeys = Object.keys(toolState).slice(0);
|
||||||
|
|
||||||
var viewportElements = $('.imageViewerViewport').not('.empty');
|
var viewportElements = $('.imageViewerViewport').not('.empty');
|
||||||
var seriesInstanceUIds = []; // Holds seriesInstanceUId of imageViewerViewport elements
|
var seriesInstanceUids = []; // Holds seriesInstanceUid of imageViewerViewport elements
|
||||||
viewportElements.each(function(index, element) {
|
viewportElements.each(function(index, element) {
|
||||||
var enabledElement = cornerstone.getEnabledElement(element);
|
var enabledElement = cornerstone.getEnabledElement(element);
|
||||||
var series = cornerstoneTools.metaData.get('series', enabledElement.image.imageId);
|
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
|
// Set null array for toolState data found by imageId and toolType
|
||||||
@ -18,8 +22,12 @@ clearTools = function() {
|
|||||||
var toolTypeData = toolState[imageId][toolType];
|
var toolTypeData = toolState[imageId][toolType];
|
||||||
if (toolTypeData && toolTypeData.data.length > 0) {
|
if (toolTypeData && toolTypeData.data.length > 0) {
|
||||||
var series = cornerstoneTools.metaData.get('series', imageId);
|
var series = cornerstoneTools.metaData.get('series', imageId);
|
||||||
// If seriesInstanceUid is found in seriesInstanceUIds, set toolState data as null
|
if (!series) {
|
||||||
if (seriesInstanceUIds.indexOf(series.seriesInstanceUid) > -1) {
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If seriesInstanceUid is found in seriesInstanceUids, set toolState data as null
|
||||||
|
if (seriesInstanceUids.indexOf(series.seriesInstanceUid) > -1) {
|
||||||
toolState[imageId][toolType] = {
|
toolState[imageId][toolType] = {
|
||||||
data: []
|
data: []
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
handleMeasurementAdded = function(e, eventData) {
|
handleMeasurementAdded = function(e, eventData) {
|
||||||
log.info('CornerstoneToolsMeasurementAdded');
|
|
||||||
var measurementData = eventData.measurementData;
|
var measurementData = eventData.measurementData;
|
||||||
|
|
||||||
switch (eventData.toolType) {
|
switch (eventData.toolType) {
|
||||||
case 'nonTarget':
|
case 'nonTarget':
|
||||||
case 'lesion':
|
case 'lesion':
|
||||||
|
log.info('CornerstoneToolsMeasurementAdded');
|
||||||
LesionManager.updateLesionData(measurementData);
|
LesionManager.updateLesionData(measurementData);
|
||||||
TrialResponseCriteria.validateDelayed(measurementData);
|
TrialResponseCriteria.validateDelayed(measurementData);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
handleMeasurementModified = function(e, eventData) {
|
handleMeasurementModified = function(e, eventData) {
|
||||||
log.info('CornerstoneToolsMeasurementModified');
|
|
||||||
var measurementData = eventData.measurementData;
|
var measurementData = eventData.measurementData;
|
||||||
|
|
||||||
switch (eventData.toolType) {
|
switch (eventData.toolType) {
|
||||||
case 'nonTarget':
|
case 'nonTarget':
|
||||||
case 'lesion':
|
case 'lesion':
|
||||||
|
log.info('CornerstoneToolsMeasurementModified');
|
||||||
LesionManager.updateLesionData(measurementData);
|
LesionManager.updateLesionData(measurementData);
|
||||||
TrialResponseCriteria.validateDelayed(measurementData);
|
TrialResponseCriteria.validateDelayed(measurementData);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -62,7 +62,7 @@ syncMeasurementAndToolData = function(data) {
|
|||||||
measurementData.isDeleted = data.isDeleted;
|
measurementData.isDeleted = data.isDeleted;
|
||||||
measurementData.location = data.location;
|
measurementData.location = data.location;
|
||||||
measurementData.locationUID = data.locationUID;
|
measurementData.locationUID = data.locationUID;
|
||||||
measurementData.patientId = patientId;
|
measurementData.patientId = data.patientId;
|
||||||
measurementData.visible = data.visible;
|
measurementData.visible = data.visible;
|
||||||
measurementData.active = data.active;
|
measurementData.active = data.active;
|
||||||
measurementData.uid = data.uid;
|
measurementData.uid = data.uid;
|
||||||
|
|||||||
@ -33,5 +33,8 @@ function dblClickOnStudy(data) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add the Timepoint name to the Patient name to create the tab title
|
||||||
|
title += ' ' + getTimepointName(timepoint);
|
||||||
|
|
||||||
openNewTabWithTimepoint(timepoint.timepointId, title);
|
openNewTabWithTimepoint(timepoint.timepointId, title);
|
||||||
}
|
}
|
||||||
@ -29,7 +29,6 @@ Package.onUse(function(api) {
|
|||||||
// Client-side collections
|
// Client-side collections
|
||||||
api.addFiles('client/collections/LesionLocations.js', 'client');
|
api.addFiles('client/collections/LesionLocations.js', 'client');
|
||||||
api.addFiles('client/collections/LocationResponses.js', 'client');
|
api.addFiles('client/collections/LocationResponses.js', 'client');
|
||||||
api.addFiles('client/collections/PatientLocations.js', 'client');
|
|
||||||
|
|
||||||
// Additional Custom Cornerstone Tools for Lesion Tracker
|
// Additional Custom Cornerstone Tools for Lesion Tracker
|
||||||
api.addFiles('client/compatibility/lesionTool.js', 'client', {
|
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.styl', 'client');
|
||||||
api.addFiles('client/components/nonTargetResponseDialog/nonTargetResponseDialog.js', '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.html', 'client');
|
||||||
api.addFiles('client/components/lesionTrackerWorklistStudy/lesionTrackerWorklistStudy.styl', 'client');
|
api.addFiles('client/components/lesionTrackerWorklistStudy/lesionTrackerWorklistStudy.styl', 'client');
|
||||||
api.addFiles('client/components/lesionTrackerWorklistStudy/lesionTrackerWorklistStudy.js', 'client');
|
api.addFiles('client/components/lesionTrackerWorklistStudy/lesionTrackerWorklistStudy.js', 'client');
|
||||||
@ -186,7 +182,6 @@ Package.onUse(function(api) {
|
|||||||
api.export('ValidationErrors', 'client');
|
api.export('ValidationErrors', 'client');
|
||||||
api.export('LesionLocations', 'client');
|
api.export('LesionLocations', 'client');
|
||||||
api.export('LocationResponses', 'client');
|
api.export('LocationResponses', 'client');
|
||||||
api.export('PatientLocations', 'client');
|
|
||||||
|
|
||||||
// Export collections spanning both client and server
|
// Export collections spanning both client and server
|
||||||
api.export('Measurements', [ 'client', 'server' ]);
|
api.export('Measurements', [ 'client', 'server' ]);
|
||||||
|
|||||||
@ -12,6 +12,12 @@ Meteor.publish('studies', function() {
|
|||||||
return Studies.find();
|
return Studies.find();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Meteor.publish('singlePatientAssociatedStudies', function(patientId) {
|
||||||
|
return Studies.find({
|
||||||
|
patientId: patientId
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
Meteor.publish('singlePatientMeasurements', function(patientId) {
|
Meteor.publish('singlePatientMeasurements', function(patientId) {
|
||||||
return Measurements.find({
|
return Measurements.find({
|
||||||
patientId: patientId
|
patientId: patientId
|
||||||
|
|||||||
@ -49,5 +49,8 @@ Meteor.methods({
|
|||||||
},
|
},
|
||||||
removeTimepoint: function(id) {
|
removeTimepoint: function(id) {
|
||||||
Timepoints.remove(id);
|
Timepoints.remove(id);
|
||||||
|
},
|
||||||
|
removeAssociatedStudy: function(id) {
|
||||||
|
Studies.remove(id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -28,7 +28,7 @@ Template.imageThumbnail.onRendered(function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Template.imageThumbnail.helpers({
|
Template.imageThumbnail.helpers({
|
||||||
'percentComplete': function() {
|
percentComplete: function() {
|
||||||
var percentComplete = Session.get('CornerstoneThumbnailLoadProgress' + this.thumbnailIndex);
|
var percentComplete = Session.get('CornerstoneThumbnailLoadProgress' + this.thumbnailIndex);
|
||||||
if (percentComplete && percentComplete !== 100) {
|
if (percentComplete && percentComplete !== 100) {
|
||||||
return percentComplete + '%';
|
return percentComplete + '%';
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
Template.hangingProtocolButtons.helpers({
|
Template.hangingProtocolButtons.helpers({
|
||||||
'isNextAvailable': function() {
|
isNextAvailable: function() {
|
||||||
var presentationGroup = Session.get('WindowManagerPresentationGroup');
|
var presentationGroup = Session.get('WindowManagerPresentationGroup');
|
||||||
var numPresentationGroups = WindowManager.getNumPresentationGroups();
|
var numPresentationGroups = WindowManager.getNumPresentationGroups();
|
||||||
return presentationGroup < numPresentationGroups;
|
return presentationGroup < numPresentationGroups;
|
||||||
},
|
},
|
||||||
'isPreviousAvailable': function() {
|
isPreviousAvailable: function() {
|
||||||
var presentationGroup = Session.get('WindowManagerPresentationGroup');
|
var presentationGroup = Session.get('WindowManagerPresentationGroup');
|
||||||
return presentationGroup > 1;
|
return presentationGroup > 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -411,6 +411,10 @@ Meteor.startup(function() {
|
|||||||
cornerstoneTools.magnify.setConfiguration(config);
|
cornerstoneTools.magnify.setConfiguration(config);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Template.imageViewerViewport.onCreated(function() {
|
||||||
|
console.log('imageViewerViewport onCreated');
|
||||||
|
});
|
||||||
|
|
||||||
Template.imageViewerViewport.onRendered(function() {
|
Template.imageViewerViewport.onRendered(function() {
|
||||||
var templateData = Template.currentData();
|
var templateData = Template.currentData();
|
||||||
log.info('imageViewerViewport onRendered');
|
log.info('imageViewerViewport onRendered');
|
||||||
@ -455,7 +459,7 @@ Template.imageViewerViewport.onRendered(function() {
|
|||||||
// TODO: This code block might be refactored
|
// TODO: This code block might be refactored
|
||||||
// Load previous measurement study when reloading a patient
|
// Load previous measurement study when reloading a patient
|
||||||
if (!study) {
|
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
|
// Once we have retrieved the data, we sort the series' by series
|
||||||
// and instance number in ascending order
|
// and instance number in ascending order
|
||||||
if (!study) {
|
if (!study) {
|
||||||
@ -466,7 +470,7 @@ Template.imageViewerViewport.onRendered(function() {
|
|||||||
data.study = study;
|
data.study = study;
|
||||||
|
|
||||||
setSeries(data, seriesInstanceUid, templateData);
|
setSeries(data, seriesInstanceUid, templateData);
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
data.study = study;
|
data.study = study;
|
||||||
@ -482,7 +486,10 @@ Template.imageViewerViewport.onDestroyed(function() {
|
|||||||
// Try to stop any currently playing clips
|
// Try to stop any currently playing clips
|
||||||
// Otherwise the interval will continuously throw errors
|
// Otherwise the interval will continuously throw errors
|
||||||
try {
|
try {
|
||||||
cornerstoneTools.stopClip(element);
|
var enabledElement = cornerstone.getEnabledElement(element);
|
||||||
|
if (enabledElement) {
|
||||||
|
cornerstoneTools.stopClip(element);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.warn(error);
|
log.warn(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
ViewerWindows = new Meteor.Collection(null);
|
ViewerWindows = new Meteor.Collection(null);
|
||||||
|
ViewerWindows._debugName = 'ViewerWindows';
|
||||||
|
|
||||||
Template.imageViewerViewports.helpers({
|
Template.imageViewerViewports.helpers({
|
||||||
height: function() {
|
height: function() {
|
||||||
@ -10,14 +11,16 @@ Template.imageViewerViewports.helpers({
|
|||||||
return 100 / viewportColumns;
|
return 100 / viewportColumns;
|
||||||
},
|
},
|
||||||
viewerWindow: function() {
|
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 viewportRows = this.viewportRows || 1;
|
||||||
var viewportColumns = this.viewportColumns || 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) {
|
if (this.viewportRows && this.viewportColumns) {
|
||||||
viewportRows = this.viewportRows || 1;
|
viewportRows = this.viewportRows || 1;
|
||||||
viewportColumns = this.viewportColumns || 1;
|
viewportColumns = this.viewportColumns || 1;
|
||||||
@ -47,13 +50,13 @@ Template.imageViewerViewports.helpers({
|
|||||||
// Update viewerData
|
// Update viewerData
|
||||||
ViewerData[contentId].viewportRows = viewportRows;
|
ViewerData[contentId].viewportRows = viewportRows;
|
||||||
ViewerData[contentId].viewportColumns = viewportColumns;
|
ViewerData[contentId].viewportColumns = viewportColumns;
|
||||||
Session.set("ViewerData", ViewerData);
|
Session.set('ViewerData', ViewerData);
|
||||||
|
|
||||||
this.viewportRows = viewportRows;
|
this.viewportRows = viewportRows;
|
||||||
this.viewportColumns = viewportColumns;
|
this.viewportColumns = viewportColumns;
|
||||||
|
|
||||||
var numViewports = viewportRows * viewportColumns;
|
var numViewports = viewportRows * viewportColumns;
|
||||||
for (var i=0; i < numViewports; ++i) {
|
for (var i = 0; i < numViewports; ++i) {
|
||||||
var data = {
|
var data = {
|
||||||
viewportIndex: i,
|
viewportIndex: i,
|
||||||
// These two are necessary because otherwise the width and height helpers
|
// These two are necessary because otherwise the width and height helpers
|
||||||
@ -77,14 +80,17 @@ Template.imageViewerViewports.helpers({
|
|||||||
ViewerWindows.insert(data);
|
ViewerWindows.insert(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Here we will find out if we need to load any other studies into the viewer
|
// Here we will find out if we need to load any other studies into the viewer
|
||||||
|
|
||||||
// We will make a list of unique studyInstanceUids
|
// We will make a list of unique studyInstanceUids
|
||||||
var uniqueStudyInstanceUids = [];
|
var uniqueStudyInstanceUids = [];
|
||||||
|
|
||||||
// Meteor doesn't support Mongo's 'distinct' function, so we have to do this in a loop
|
// 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;
|
var studyInstanceUid = window.studyInstanceUid;
|
||||||
if (!studyInstanceUid) {
|
if (!studyInstanceUid) {
|
||||||
return;
|
return;
|
||||||
@ -99,10 +105,17 @@ Template.imageViewerViewports.helpers({
|
|||||||
uniqueStudyInstanceUids.push(studyInstanceUid);
|
uniqueStudyInstanceUids.push(studyInstanceUid);
|
||||||
|
|
||||||
// If any of the associated studies is not already loaded, load it now
|
// 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) {
|
if (!loadedStudy) {
|
||||||
// Load the study
|
// 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
|
// Sort the study's series and instances by series and instance number
|
||||||
sortStudy(study);
|
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({
|
Template.imageViewerViewports.events({
|
||||||
'CornerstoneMouseDoubleClick .imageViewerViewport': function(e) {
|
'CornerstoneMouseDoubleClick .imageViewerViewport': function(e) {
|
||||||
var container = $(".viewerMain").get(0);
|
var container = $('.viewerMain').get(0);
|
||||||
var data;
|
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
|
// If there is more than one viewport on screen
|
||||||
// And one of them is double-clicked, it should be rendered alone
|
// And one of them is double-clicked, it should be rendered alone
|
||||||
|
|||||||
@ -59,7 +59,7 @@ function resultDataToStudyMetadata(resultData) {
|
|||||||
*/
|
*/
|
||||||
Services.DIMSE.Instances = function(studyInstanceUid) {
|
Services.DIMSE.Instances = function(studyInstanceUid) {
|
||||||
//var url = buildUrl(server, 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("DIMSE Instance retrieval");
|
||||||
console.log(result);
|
console.log(result);
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
<template name="tabContent">
|
<template name="tabContent">
|
||||||
<div role="tabpanel" class="tab-pane {{active}}" id="{{contentid}}">
|
<div role="tabpanel" class="tab-pane {{active}}" id="{{contentid}}">
|
||||||
{{ >loadingText }}
|
{{>loadingText}}
|
||||||
|
<div class="viewerContainer">
|
||||||
|
<!-- This extra viewerContainer div only exists to be destroyed when switching tabs.
|
||||||
|
It is a workaround because Meteor's onDestroyed and destruction handlers don't fire
|
||||||
|
when removing the actual template from the DOM.-->
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -2,3 +2,7 @@
|
|||||||
.loadingTextDiv
|
.loadingTextDiv
|
||||||
h5
|
h5
|
||||||
color: #777
|
color: #777
|
||||||
|
|
||||||
|
.viewerContainer
|
||||||
|
height: 100%
|
||||||
|
width: 100%
|
||||||
@ -7,9 +7,9 @@
|
|||||||
{{ >tabTitle }}
|
{{ >tabTitle }}
|
||||||
{{ /each }}
|
{{ /each }}
|
||||||
</ul>
|
</ul>
|
||||||
<div class="tab-content">
|
<div id="worklistTabs" class="tab-content">
|
||||||
<div role="tabpanel" class="tab-pane active" id="worklistTab">
|
<div role="tabpanel" class="tab-pane active" id="worklistTab">
|
||||||
<div id="worklistContainer" class="container">
|
<div class="worklistContainer">
|
||||||
{{> worklistResult }}
|
{{> worklistResult }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -11,20 +11,13 @@ ViewerData = Session.get('ViewerData') || {};
|
|||||||
|
|
||||||
// Create the WorklistTabs collection
|
// Create the WorklistTabs collection
|
||||||
WorklistTabs = new Meteor.Collection(null);
|
WorklistTabs = new Meteor.Collection(null);
|
||||||
|
WorklistTabs._debugName = 'WorklistTabs';
|
||||||
|
|
||||||
// Define the WorklistStudies Collection
|
// Define the WorklistStudies Collection
|
||||||
// This is a client-side only Collection which
|
// This is a client-side only Collection which
|
||||||
// Stores the list of studies in the Worklist
|
// Stores the list of studies in the Worklist
|
||||||
WorklistStudies = new Meteor.Collection(null);
|
WorklistStudies = new Meteor.Collection(null);
|
||||||
|
WorklistStudies._debugName = 'WorklistStudies';
|
||||||
Template.worklist.onCreated(function() {
|
|
||||||
var self = this;
|
|
||||||
if (Worklist.subscriptions) {
|
|
||||||
Worklist.subscriptions.forEach(function(collectionName) {
|
|
||||||
self.subscribe(collectionName);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Template.worklist.onRendered(function() {
|
Template.worklist.onRendered(function() {
|
||||||
// If there is a tab set as active in the Session,
|
// If there is a tab set as active in the Session,
|
||||||
|
|||||||
@ -9,37 +9,18 @@ body
|
|||||||
-ms-user-select: none
|
-ms-user-select: none
|
||||||
user-select: none
|
user-select: none
|
||||||
|
|
||||||
input.worklist-search
|
|
||||||
height: 25px
|
|
||||||
background-color: #888888
|
|
||||||
width: 100%
|
|
||||||
|
|
||||||
#tblStudyList
|
#tblStudyList
|
||||||
tr
|
tr
|
||||||
height: 20px
|
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
|
#worklistTab
|
||||||
background-color: #202020
|
background-color: #202020
|
||||||
|
|
||||||
#worklistContainer
|
.worklistContainer
|
||||||
background-color: #202020
|
background-color: #202020
|
||||||
|
margin: 0 auto
|
||||||
color: white
|
color: white
|
||||||
width:90%
|
width: 90%
|
||||||
padding-top: 10px
|
padding-top: 10px
|
||||||
|
|
||||||
#tabs
|
#tabs
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<template name="worklistResult">
|
<template name="worklistResult">
|
||||||
<table id="tblStudyList" class="table table-striped noselect">
|
<table id="tblStudyList" class="worklistResult table table-striped noselect">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>
|
<th>
|
||||||
|
|||||||
@ -115,6 +115,20 @@ function search() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Template.worklistResult.onCreated(function() {
|
||||||
|
console.log('WorklistResult onCreated!');
|
||||||
|
var self = this;
|
||||||
|
if (Worklist.subscriptions) {
|
||||||
|
Worklist.subscriptions.forEach(function(collectionName) {
|
||||||
|
self.subscribe(collectionName);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Template.worklistResult.onDestroyed(function() {
|
||||||
|
console.log('WorklistResult onDestroyed!');
|
||||||
|
});
|
||||||
|
|
||||||
Template.worklistResult.events({
|
Template.worklistResult.events({
|
||||||
'keydown input': function(e) {
|
'keydown input': function(e) {
|
||||||
if (e.which === 13) { // Enter
|
if (e.which === 13) { // Enter
|
||||||
|
|||||||
@ -15,8 +15,14 @@ table#tblStudyList
|
|||||||
#numImages.worklist-search, #modality.worklist-search
|
#numImages.worklist-search, #modality.worklist-search
|
||||||
visibility: hidden
|
visibility: hidden
|
||||||
|
|
||||||
|
input.worklist-search
|
||||||
|
height: 25px
|
||||||
|
background-color: #888888
|
||||||
|
width: 100%
|
||||||
|
|
||||||
tbody
|
tbody
|
||||||
tr
|
tr
|
||||||
|
height: 20px
|
||||||
padding: 4px
|
padding: 4px
|
||||||
border: 1px solid #282828
|
border: 1px solid #282828
|
||||||
background-color: black
|
background-color: black
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
// Maybe we should use regular Worklist collection?
|
// Maybe we should use regular Worklist collection?
|
||||||
WorklistSelectedStudies = new Meteor.Collection(null);
|
WorklistSelectedStudies = new Meteor.Collection(null);
|
||||||
|
WorklistSelectedStudies._debugName = 'WorklistSelectedStudies';
|
||||||
|
|
||||||
function handleShiftClick(studyRow, data) {
|
function handleShiftClick(studyRow, data) {
|
||||||
log.info('shiftKey');
|
log.info('shiftKey');
|
||||||
|
|||||||
@ -12,8 +12,6 @@ var StudyMetaData = {};
|
|||||||
* @param failCallback The callback function to be executed when the study retrieval has failed
|
* @param failCallback The callback function to be executed when the study retrieval has failed
|
||||||
*/
|
*/
|
||||||
getStudyMetadata = function(studyInstanceUid, doneCallback, failCallback) {
|
getStudyMetadata = function(studyInstanceUid, doneCallback, failCallback) {
|
||||||
log.info('worklistStudy getStudyMetadata');
|
|
||||||
|
|
||||||
// If the StudyMetaData cache already has data related to this
|
// If the StudyMetaData cache already has data related to this
|
||||||
// studyInstanceUid, then we should fire the doneCallback with this data
|
// studyInstanceUid, then we should fire the doneCallback with this data
|
||||||
// and stop here.
|
// and stop here.
|
||||||
@ -26,6 +24,8 @@ getStudyMetadata = function(studyInstanceUid, doneCallback, failCallback) {
|
|||||||
// If no study metadata is in the cache variable, we need to retrieve it from
|
// If no study metadata is in the cache variable, we need to retrieve it from
|
||||||
// the server with a call.
|
// the server with a call.
|
||||||
Meteor.call('GetStudyMetadata', studyInstanceUid, function(error, study) {
|
Meteor.call('GetStudyMetadata', studyInstanceUid, function(error, study) {
|
||||||
|
log.info('worklistStudy getStudyMetadata: ' + studyInstanceUid);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
log.warn(error);
|
log.warn(error);
|
||||||
failCallback(error);
|
failCallback(error);
|
||||||
|
|||||||
@ -8,6 +8,7 @@ switchToTab = function(contentId) {
|
|||||||
if (!contentId) {
|
if (!contentId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info('Switching to tab: ' + contentId);
|
log.info('Switching to tab: ' + contentId);
|
||||||
|
|
||||||
// Use Bootstrap's Tab JavaScript to show the contents of the current tab
|
// Use Bootstrap's Tab JavaScript to show the contents of the current tab
|
||||||
@ -15,7 +16,8 @@ switchToTab = function(contentId) {
|
|||||||
$('.tabTitle a[data-target="#' + contentId + '"]').tab('show');
|
$('.tabTitle a[data-target="#' + contentId + '"]').tab('show');
|
||||||
|
|
||||||
// Remove any previous Viewers from the DOM
|
// Remove any previous Viewers from the DOM
|
||||||
$('#viewer').remove();
|
$('.viewerContainer').remove();
|
||||||
|
$('.worklistContainer').remove();
|
||||||
|
|
||||||
// Update the 'activeContentId' variable in Session
|
// Update the 'activeContentId' variable in Session
|
||||||
Session.set('activeContentId', contentId);
|
Session.set('activeContentId', contentId);
|
||||||
@ -23,7 +25,16 @@ switchToTab = function(contentId) {
|
|||||||
// If we are switching to the Worklist tab, reset any CSS styles
|
// If we are switching to the Worklist tab, reset any CSS styles
|
||||||
// that have been applied to prevent scrolling in the Viewer.
|
// that have been applied to prevent scrolling in the Viewer.
|
||||||
// Then stop here, since nothing needs to be re-rendered.
|
// Then stop here, since nothing needs to be re-rendered.
|
||||||
|
var container;
|
||||||
if (contentId === 'worklistTab') {
|
if (contentId === 'worklistTab') {
|
||||||
|
container = $('.tab-content').find('#worklistTab').get(0);
|
||||||
|
var worklistContainer = document.createElement('div');
|
||||||
|
worklistContainer.classList.add('worklistContainer');
|
||||||
|
container.appendChild(worklistContainer);
|
||||||
|
|
||||||
|
// Use Blaze to render the WorklistResult Template into the container
|
||||||
|
Blaze.render(Template.worklistResult, worklistContainer);
|
||||||
|
|
||||||
document.body.style.overflow = null;
|
document.body.style.overflow = null;
|
||||||
document.body.style.height = null;
|
document.body.style.height = null;
|
||||||
document.body.style.minWidth = null;
|
document.body.style.minWidth = null;
|
||||||
@ -67,10 +78,13 @@ switchToTab = function(contentId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove the loading text template that is inside the tab container by default
|
// Remove the loading text template that is inside the tab container by default
|
||||||
|
var viewerContainer = document.createElement('div');
|
||||||
|
viewerContainer.classList.add('viewerContainer');
|
||||||
container.innerHTML = '';
|
container.innerHTML = '';
|
||||||
|
container.appendChild(viewerContainer);
|
||||||
|
|
||||||
// Use Blaze to render the Viewer Template into the container
|
// Use Blaze to render the Viewer Template into the container
|
||||||
UI.renderWithData(Template.viewer, data, container);
|
Blaze.renderWithData(Template.viewer, data, viewerContainer);
|
||||||
|
|
||||||
// Retrieve the DOM element of the viewer
|
// Retrieve the DOM element of the viewer
|
||||||
var imageViewer = $('#viewer');
|
var imageViewer = $('#viewer');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user