Added data persistence for lesion measurements and timepoints

This commit is contained in:
Erik Ziegler 2015-11-19 21:11:41 +01:00
parent 1facbfb0b1
commit 0af0069d69
15 changed files with 157 additions and 73 deletions

View File

@ -16,7 +16,6 @@ standard-minifiers # JS/CSS minifiers run for production mode
es5-shim # ECMAScript 5 compatibility for older browsers. es5-shim # ECMAScript 5 compatibility for older browsers.
ecmascript # Enable ECMAScript2015+ syntax in app code ecmascript # Enable ECMAScript2015+ syntax in app code
autopublish # Publish all data to the clients (for prototyping)
insecure # Allow all DB writes from clients (for prototyping) insecure # Allow all DB writes from clients (for prototyping)
cornerstone cornerstone
worklist worklist

View File

@ -2,7 +2,6 @@ accounts-base@1.2.2
accounts-password@1.1.4 accounts-password@1.1.4
anti:i18n@0.4.3 anti:i18n@0.4.3
arsnebula:reactive-promise@0.9.1 arsnebula:reactive-promise@0.9.1
autopublish@1.0.4
autoupdate@1.2.4 autoupdate@1.2.4
babel-compiler@5.8.24_1 babel-compiler@5.8.24_1
babel-runtime@0.1.4 babel-runtime@0.1.4

View File

@ -16,12 +16,8 @@ function resizeViewports() {
}, 1); }, 1);
} }
Template.viewer.helpers({
'studyDateIsShown':function(){
return {studyDateIsShown: true};
}
});
Template.viewer.onCreated(function() { Template.viewer.onCreated(function() {
var self = this;
log.info("viewer onCreated"); log.info("viewer onCreated");
OHIF = { OHIF = {
@ -88,6 +84,7 @@ Template.viewer.onCreated(function() {
Session.set('activeViewport', ViewerData[contentId].activeViewport || 0); Session.set('activeViewport', ViewerData[contentId].activeViewport || 0);
// Update the ViewerStudies collection with the loaded studies // Update the ViewerStudies collection with the loaded studies
ViewerStudies = new Meteor.Collection(null); ViewerStudies = new Meteor.Collection(null);
this.data.studies.forEach(function(study) { this.data.studies.forEach(function(study) {
@ -95,9 +92,53 @@ Template.viewer.onCreated(function() {
ViewerStudies.insert(study); ViewerStudies.insert(study);
}); });
var patientId = this.data.studies[0].patientId;
Session.set('patientId', patientId);
timepointsAdded = false;
self.autorun(function() {
var patientId = Session.get('patientId');
self.subscribe('timepoints', patientId);
self.subscribe('measurements', patientId);
if (!self.subscriptionsReady()) {
return;
}
if (timepointsAdded === true) {
return;
}
ViewerStudies.find().forEach(function(study) {
var timepoint = Timepoints.findOne({timepointName: study.studyDate});
if (timepoint) {
log.warn("A timepoint with that study date already exists!");
return;
}
var timepointID = uuid.v4();
var testTimepoint = Timepoints.findOne({});
if (testTimepoint && testTimepoint.patientId !== study.patientId) {
log.warn("Timepoints collection related to the wrong subject");
return;
}
log.info('Inserting a new timepoint');
Timepoints.insert({
patientId: study.patientId,
timepointID: timepointID,
timepointName: study.studyDate
});
});
timepointsAdded = true;
});
OHIF.viewer.updateImageSynchronizer = new cornerstoneTools.Synchronizer("CornerstoneNewImage", cornerstoneTools.updateImageSynchronizer); OHIF.viewer.updateImageSynchronizer = new cornerstoneTools.Synchronizer("CornerstoneNewImage", cornerstoneTools.updateImageSynchronizer);
}); });
Template.viewer.onDestroyed(function() { Template.viewer.onDestroyed(function() {
log.info("onDestroyed"); log.info("onDestroyed");
OHIF.viewer.updateImageSynchronizer.destroy(); OHIF.viewer.updateImageSynchronizer.destroy();

View File

@ -0,0 +1,2 @@
Timepoints = new Meteor.Collection('timepoints');
Measurements = new Meteor.Collection('measurements');

View File

@ -1,5 +1,4 @@
var activeLesionMeasurementData; var activeLesionMeasurementData;
var timepointID;
var cornerstoneTools = (function($, cornerstone, cornerstoneMath, cornerstoneTools) { var cornerstoneTools = (function($, cornerstone, cornerstoneMath, cornerstoneTools) {
"use strict"; "use strict";
@ -40,7 +39,7 @@ var cornerstoneTools = (function($, cornerstone, cornerstoneMath, cornerstoneToo
var measurementData = createNewMeasurement(mouseEventData); var measurementData = createNewMeasurement(mouseEventData);
var eventData = { var eventData = {
mouseButtonMask: mouseEventData.which, mouseButtonMask: mouseEventData.which
}; };
// associate this data with this imageId so we can render it and manipulate it // associate this data with this imageId so we can render it and manipulate it
@ -188,10 +187,6 @@ var cornerstoneTools = (function($, cornerstone, cornerstoneMath, cornerstoneToo
return; return;
} }
updateLesions(toolData, eventData);
}
function updateLesions(toolData, eventData) {
// we have tool data for this element - iterate over each one and draw it // we have tool data for this element - iterate over each one and draw it
var context = eventData.canvasContext.canvas.getContext('2d'); var context = eventData.canvasContext.canvas.getContext('2d');
context.setTransform(1, 0, 0, 1, 0, 0); context.setTransform(1, 0, 0, 1, 0, 0);

View File

@ -9,6 +9,10 @@ var measurementManagerDAL = (function() {
// Add timepoint data to Measurements collection // Add timepoint data to Measurements collection
function addTimepointData(lesionData) { function addTimepointData(lesionData) {
var timepoints = Timepoints.find().fetch(); var timepoints = Timepoints.find().fetch();
var study = cornerstoneTools.metaData.get('study', lesionData.imageId);
var series = cornerstoneTools.metaData.get('study', lesionData.imageId);
var timepointsObject = {}; var timepointsObject = {};
for (var i = 0; i < timepoints.length; i++) { for (var i = 0; i < timepoints.length; i++) {
@ -17,10 +21,12 @@ var measurementManagerDAL = (function() {
var timepointObject; var timepointObject;
if (timepointId === lesionTimepointId) { if (timepointId === lesionTimepointId) {
// Add real mesurement // Add real measurement
timepointObject = { timepointObject = {
longestDiameter: lesionData.measurementText, longestDiameter: lesionData.measurementText,
imageId: lesionData.imageId, imageId: lesionData.imageId,
studyInstanceUid: study.instanceUid,
seriesInstanceUid: series.instanceUid
}; };
} else { } else {
// Add null measurement // Add null measurement
@ -33,6 +39,7 @@ var measurementManagerDAL = (function() {
} }
var lesionDataObject = { var lesionDataObject = {
patientId: timepoints[0].patientId,
lesionUID: uuid.v4(), lesionUID: uuid.v4(),
number: Measurements.find().count() + 1, number: Measurements.find().count() + 1,
lesionNumber: lesionData.lesionNumber, lesionNumber: lesionData.lesionNumber,
@ -47,10 +54,10 @@ var measurementManagerDAL = (function() {
// Update timepoint data in Measurements collection // Update timepoint data in Measurements collection
function updateTimepointData(lesionData) { function updateTimepointData(lesionData) {
// Find the specific lesion to be updated // Find the specific lesion to be updated
var measurement = Measurements.find({ var measurement = Measurements.findOne({
lesionNumber: lesionData.lesionNumber, lesionNumber: lesionData.lesionNumber,
isTarget: lesionData.isTarget isTarget: lesionData.isTarget
}).fetch()[0]; });
// If no such lesion exists, stop here // If no such lesion exists, stop here
if (!measurement) { if (!measurement) {
@ -65,10 +72,7 @@ var measurementManagerDAL = (function() {
timepoints[timepointID].longestDiameter = lesionData.measurementText; timepoints[timepointID].longestDiameter = lesionData.measurementText;
timepoints[timepointID].imageId = lesionData.imageId; timepoints[timepointID].imageId = lesionData.imageId;
Measurements.update({ Measurements.update(measurement._id, {
lesionNumber: lesionData.lesionNumber,
isTarget: lesionData.isTarget
}, {
$set: { $set: {
timepoints: timepoints timepoints: timepoints
} }
@ -81,13 +85,14 @@ var measurementManagerDAL = (function() {
lesionNumber: lesionData.lesionNumber, lesionNumber: lesionData.lesionNumber,
isTarget: lesionData.isTarget isTarget: lesionData.isTarget
}); });
if (timepointData) { if (timepointData) {
return true; return true;
} }
return false; return false;
} }
// Adds new timepoint item to tiemepoints array // Adds new timepoint item to timepoints array
function addLesionData(lesionData) { function addLesionData(lesionData) {
if (hasTimepointData(lesionData)) { if (hasTimepointData(lesionData)) {
// Update data // Update data
@ -115,6 +120,10 @@ var measurementManagerDAL = (function() {
var measurement = measurements[i]; var measurement = measurements[i];
var timepoints = measurement.timepoints; var timepoints = measurement.timepoints;
if (!timepoints[timepointID]) {
return;
}
if (timepoints[timepointID].longestDiameter === '') { if (timepoints[timepointID].longestDiameter === '') {
return measurement.lesionNumber; return measurement.lesionNumber;
} else { } else {
@ -130,10 +139,10 @@ var measurementManagerDAL = (function() {
return; return;
} }
var measurement = Measurements.find({ var measurement = Measurements.findOne({
lesionNumber: lesionData.lesionNumber, lesionNumber: lesionData.lesionNumber,
isTarget: lesionData.isTarget isTarget: lesionData.isTarget
}).fetch()[0]; });
return measurement.locationUID; return measurement.locationUID;
} }

View File

@ -174,20 +174,6 @@ LesionLocations.insert({
description: "" description: ""
}); });
var lastAddedLesionData;
Template.lesionLocationDialog.onRendered(function() {
// Observe Measurements Collection Changes
Measurements.find().observe({
added: function(lesionData) {
lastAddedLesionData = lesionData;
},
changed: function(lesionData) {
console.log("lesionData has changed!");
}
});
});
Template.lesionLocationDialog.helpers({ Template.lesionLocationDialog.helpers({
'lesionLocations': function() { 'lesionLocations': function() {
return LesionLocations.find(); return LesionLocations.find();

View File

@ -1,17 +1,20 @@
Measurements = new Meteor.Collection(null);
Timepoints = new Meteor.Collection(null);
// Activate selected lesions when lesion table row is clicked // Activate selected lesions when lesion table row is clicked
function updateLesions(e) { function updateLesions(e) {
// lesionNumber of measurement = id of row // lesionNumber of measurement = id of row
var lesionNumber = parseInt($(e.currentTarget).attr("id"), 10); var lesionNumber = parseInt($(e.currentTarget).attr("id"), 10);
// TODO= Clarify this
var isTarget = $(e.currentTarget).find('td').eq(2).html().trim() === 'N'?false:true; var isTarget = $(e.currentTarget).find('td').eq(2).html().trim() === 'N'?false:true;
// Find data for specific lesion // Find data for specific lesion
var measurementData = Measurements.find({ var measurementData = Measurements.findOne({
lesionNumber: lesionNumber, lesionNumber: lesionNumber,
isTarget: isTarget isTarget: isTarget
}).fetch()[0]; });
if (!measurementData) {
return;
}
var timepoints = measurementData.timepoints; var timepoints = measurementData.timepoints;
@ -19,7 +22,14 @@ function updateLesions(e) {
// Get the timepointID related to the image viewer viewport // Get the timepointID related to the image viewer viewport
// from the DOM itself. This will be changed later when a // from the DOM itself. This will be changed later when a
// real association between viewports and timepoints is created. // real association between viewports and timepoints is created.
var timepointID = $(element).data('timepointID'); var enabledElement = cornerstone.getEnabledElement(element);
var study = cornerstoneTools.metaData.get('study', enabledElement.image.imageId);
var timepoint = Timepoints.findOne({timepointName: study.date});
if (!timepoint) {
return;
}
var timepointID = timepoint.timepointID;
var timepointObject = timepoints[timepointID]; var timepointObject = timepoints[timepointID];
// Defines event data // Defines event data
@ -54,28 +64,6 @@ function updateLesions(e) {
}); });
} }
Template.lesionTable.onRendered(function() {
// Observe ViewerStudies Collection Changes
// Note: This may not be the best place for this
ViewerStudies.find().observe({
added: function(study) {
log.info('ViewerStudies added to');
var timepointID = uuid.v4();
var timepoint = Timepoints.findOne({timepointName: study.studyDate});
if (timepoint) {
log.warn("A timepoint with that study date already exists!");
return;
}
Timepoints.insert({
timepointID: timepointID,
timepointName: study.studyDate
});
}
});
});
Template.lesionTable.helpers({ Template.lesionTable.helpers({
'measurement': function() { 'measurement': function() {
return Measurements.find(); return Measurements.find();

View File

@ -2,6 +2,12 @@ Template.lesionTableTimepointCell.helpers({
'longestDiameter': function() { 'longestDiameter': function() {
// Search Measurements by lesion and timepoint // Search Measurements by lesion and timepoint
var lesionData = Template.parentData(1); var lesionData = Template.parentData(1);
if (!lesionData ||
!lesionData.timepoints ||
!lesionData.timepoints[this.timepointID]) {
return;
}
return lesionData.timepoints[this.timepointID].longestDiameter; return lesionData.timepoints[this.timepointID].longestDiameter;
} }
}); });

View File

@ -1,8 +1,16 @@
// This event sets lesion number for new lesion // This event sets lesion number for new lesion
function setLesionNumberCallback(measurementData, eventData, doneCallback) { function setLesionNumberCallback(measurementData, eventData, doneCallback) {
// Get the current element's timepointID from the study date metadata
var element = eventData.element;
var enabledElement = cornerstone.getEnabledElement(element);
var study = cornerstoneTools.metaData.get('study', enabledElement.image.imageId);
var timepoint = Timepoints.findOne({timepointName: study.date});
if (!timepoint) {
return;
}
measurementData.timepointID = timepoint.timepointID;
measurementData.timepointID = $(eventData.element).data('timepointID');
// Get a lesion number for this lesion, depending on whether or not the same lesion previously // Get a lesion number for this lesion, depending on whether or not the same lesion previously
// exists at a different timepoint // exists at a different timepoint
var lesionNumber = measurementManagerDAL.getNewLesionNumber(measurementData.timepointID, isTarget=false); var lesionNumber = measurementManagerDAL.getNewLesionNumber(measurementData.timepointID, isTarget=false);
@ -16,7 +24,7 @@ function setLesionNumberCallback(measurementData, eventData, doneCallback) {
// If there already exists a lesion with this specific lesion number, // If there already exists a lesion with this specific lesion number,
// related to the chosen location. // related to the chosen location.
function getNonTargetLesionLocationCallback(measurementData, eventData, doneCallback) { function getNonTargetLesionLocationCallback(measurementData, eventData) {
// Get the non-target lesion location dialog // Get the non-target lesion location dialog
var nonTargetlesionDialog = $("#nonTargetLesionLocationDialog"); var nonTargetlesionDialog = $("#nonTargetLesionLocationDialog");

View File

@ -83,6 +83,27 @@ Template.studyDateList.events({
// with the value True, and insert it into the ViewerStudies Collection // with the value True, and insert it into the ViewerStudies Collection
study.selected = true; study.selected = true;
ViewerStudies.insert(study); ViewerStudies.insert(study);
var timepointID = uuid.v4();
var timepoint = Timepoints.findOne({timepointName: study.studyDate});
if (timepoint) {
log.warn("A timepoint with that study date already exists!");
return;
}
var testTimepoint = Timepoints.findOne({});
if (testTimepoint && testTimepoint.patientId !== study.patientId) {
log.warn("Timepoints collection related to the wrong subject");
return;
}
log.info('Inserting a new timepoint');
Timepoints.insert({
patientId: study.patientId,
timepointID: timepointID,
timepointName: study.studyDate
});
}); });
} }
}); });

View File

@ -0,0 +1,3 @@
// Create package logger using loglevel
// https://atmospherejs.com/spacejamio/loglevel
log = loglevel.createPackageLogger('lesiontracker', defaultLevel = 'info');

View File

@ -10,10 +10,13 @@ Package.onUse(function (api) {
api.use('standard-app-packages'); api.use('standard-app-packages');
api.use('jquery'); api.use('jquery');
api.use('stylus'); api.use('stylus');
api.use('practicalmeteor:loglevel');
// Our custom package // Our custom package
api.use('cornerstone'); api.use('cornerstone');
api.addFiles('log.js', ['client', 'server']);
api.addFiles('compatibility/lesionTool.js', 'client', {bare: true}); api.addFiles('compatibility/lesionTool.js', 'client', {bare: true});
api.addFiles('compatibility/nonTargetTool.js', 'client', {bare: true}); api.addFiles('compatibility/nonTargetTool.js', 'client', {bare: true});
api.addFiles('compatibility/measurementManagerDAL.js', 'client', {bare: true}); api.addFiles('compatibility/measurementManagerDAL.js', 'client', {bare: true});
@ -43,14 +46,15 @@ Package.onUse(function (api) {
api.addFiles('components/studyDateList/studyDateList.styl', 'client'); api.addFiles('components/studyDateList/studyDateList.styl', 'client');
api.addFiles('components/studyDateList/studyDateList.js', 'client'); api.addFiles('components/studyDateList/studyDateList.js', 'client');
// Server functions
api.addFiles('server/collections.js', 'server');
// Both client and server functions
api.addFiles('both/collections.js', ['client', 'server']);
// Library functions // Library functions
api.addFiles('lib/uuid.js', 'client'); api.addFiles('lib/uuid.js', 'client');
api.export('Measurements', 'client'); api.export('Measurements', ['client', 'server']);
api.export('Timepoints', 'client'); api.export('Timepoints', ['client', 'server']);
}); });

View File

@ -0,0 +1,24 @@
Meteor.publish('timepoints', function(patientId) {
console.log('Publish timepoints');
console.log('patientId ' + patientId);
return Timepoints.find({patientId: patientId});
});
Meteor.publish('measurements', function(patientId) {
console.log('Publish measurements');
console.log('patientId ' + patientId);
return Measurements.find({patientId: patientId});
});
// Temporary fix to drop all Collections on server restart
// http://stackoverflow.com/questions/23891631/meteor-how-can-i-drop-all-mongo-collections-and-clear-all-data-on-startup
Meteor.startup(function(){
var globalObject=Meteor.isClient?window:global;
for(var property in globalObject){
var object=globalObject[property];
if(object instanceof Meteor.Collection){
object.remove({});
}
}
});

View File

@ -190,7 +190,6 @@ function thumbnailDragEndHandler(e, target) {
} }
Template.thumbnailEntry.onRendered(function() { Template.thumbnailEntry.onRendered(function() {
console.log(this.data);
var entry = this.find('.thumbnailEntry'); var entry = this.find('.thumbnailEntry');
$(entry).data('seriesInstanceUid', Template.parentData(0).seriesInstanceUid); $(entry).data('seriesInstanceUid', Template.parentData(0).seriesInstanceUid);
$(entry).data('studyInstanceUid', Template.parentData(1).studyInstanceUid); $(entry).data('studyInstanceUid', Template.parentData(1).studyInstanceUid);