Added persistent single-image measurments (length, ellipse) (LT-37, LT-156)
This commit is contained in:
parent
b624d12762
commit
36ff46be41
@ -115,6 +115,7 @@ Template.viewer.onCreated(function() {
|
||||
self.subscribe('singlePatientAssociatedStudies', dataContext.studies[0].patientId);
|
||||
self.subscribe('singlePatientTimepoints', dataContext.studies[0].patientId);
|
||||
self.subscribe('singlePatientMeasurements', dataContext.studies[0].patientId);
|
||||
self.subscribe('singlePatientImageMeasurements', dataContext.studies[0].patientId);
|
||||
|
||||
var subscriptionsReady = self.subscriptionsReady();
|
||||
log.info('autorun viewer.js. Ready: ' + subscriptionsReady);
|
||||
@ -145,6 +146,39 @@ Template.viewer.onCreated(function() {
|
||||
}
|
||||
});
|
||||
|
||||
ImageMeasurements.find().observe({
|
||||
added: function(data) {
|
||||
if (data.clientId === ClientId) {
|
||||
return;
|
||||
}
|
||||
|
||||
syncImageMeasurementAndToolData(data);
|
||||
|
||||
// Update each displayed viewport
|
||||
updateAllViewports();
|
||||
},
|
||||
changed: function(data) {
|
||||
if (data.clientId === ClientId) {
|
||||
return;
|
||||
}
|
||||
|
||||
syncImageMeasurementAndToolData(data);
|
||||
|
||||
// Update each displayed viewport
|
||||
updateAllViewports();
|
||||
},
|
||||
removed: function(data) {
|
||||
if (data.clientId === ClientId) {
|
||||
return;
|
||||
}
|
||||
|
||||
removeToolDataWithMeasurementId(data.imageId, data.toolType, data.id);
|
||||
|
||||
// Update each displayed viewport
|
||||
updateAllViewports();
|
||||
}
|
||||
});
|
||||
|
||||
Measurements.find().observe({
|
||||
added: function(data) {
|
||||
if (data.clientId === ClientId) {
|
||||
@ -170,10 +204,7 @@ Template.viewer.onCreated(function() {
|
||||
}
|
||||
|
||||
// Update each displayed viewport
|
||||
var viewports = $('.imageViewerViewport').not('.empty');
|
||||
viewports.each(function(index, element) {
|
||||
cornerstone.updateImage(element);
|
||||
});
|
||||
updateAllViewports();
|
||||
},
|
||||
changed: function(data) {
|
||||
if (data.clientId === ClientId) {
|
||||
@ -188,10 +219,7 @@ Template.viewer.onCreated(function() {
|
||||
syncMeasurementAndToolData(data);
|
||||
|
||||
// Update each displayed viewport
|
||||
var viewports = $('.imageViewerViewport').not('.empty');
|
||||
viewports.each(function(index, element) {
|
||||
cornerstone.updateImage(element);
|
||||
});
|
||||
updateAllViewports();
|
||||
|
||||
TrialResponseCriteria.validateAllDelayed();
|
||||
},
|
||||
@ -235,10 +263,7 @@ Template.viewer.onCreated(function() {
|
||||
});
|
||||
|
||||
// Update each displayed viewport
|
||||
var viewports = $('.imageViewerViewport').not('.empty');
|
||||
viewports.each(function(index, element) {
|
||||
cornerstone.updateImage(element);
|
||||
});
|
||||
updateAllViewports();
|
||||
|
||||
ValidationErrors.remove({
|
||||
measurementId: data._id
|
||||
|
||||
@ -46,6 +46,13 @@ Template.viewerMain.helpers({
|
||||
iconClasses: 'fa fa-arrows-v'
|
||||
});
|
||||
|
||||
buttonData.push({
|
||||
id: 'ellipticalRoi',
|
||||
title: 'Elliptical ROI Measurement',
|
||||
classes: 'imageViewerTool',
|
||||
iconClasses: 'fa fa-circle-o'
|
||||
});
|
||||
|
||||
buttonData.push({
|
||||
id: 'lesion',
|
||||
title: 'Target Tool',
|
||||
|
||||
@ -3136,6 +3136,8 @@ if (typeof cornerstoneTools === 'undefined') {
|
||||
|
||||
if (!isNaN(meanStdDev.mean) && !isNaN(meanStdDev.stdDev)) {
|
||||
data.meanStdDev = meanStdDev;
|
||||
data.mean = meanStdDev.mean;
|
||||
data.stdev = meanStdDev.stdev;
|
||||
}
|
||||
}
|
||||
|
||||
@ -3985,6 +3987,9 @@ if (typeof cornerstoneTools === 'undefined') {
|
||||
// Calculate the length, and create the text variable with the millimeters or pixels suffix
|
||||
var length = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
// Store the length inside the tool for outside access
|
||||
data.length = length;
|
||||
|
||||
// Set the length text suffix depending on whether or not pixelSpacing is available
|
||||
var suffix = ' mm';
|
||||
if (!eventData.image.rowPixelSpacing || !eventData.image.columnPixelSpacing) {
|
||||
@ -6646,7 +6651,7 @@ if (typeof cornerstoneTools === 'undefined') {
|
||||
// If any handle is outside the image, delete the tool data
|
||||
if (options.deleteIfHandleOutsideImage === true &&
|
||||
cornerstoneTools.anyHandlesOutsideImage(eventData, data.handles)) {
|
||||
cornerstoneTools.toolState.removeToolState(element, toolType, data);
|
||||
cornerstoneTools.removeToolState(element, toolType, data);
|
||||
}
|
||||
|
||||
cornerstone.updateImage(element);
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
Timepoints = new Meteor.Collection('timepoints');
|
||||
Studies = new Meteor.Collection('studies');
|
||||
Measurements = new Meteor.Collection('measurements');
|
||||
ImageMeasurements = new Meteor.Collection('imageMeasurements');
|
||||
|
||||
WorklistSubscriptions = ['studies', 'timepoints'];
|
||||
@ -99,7 +99,8 @@
|
||||
longestDiameter: 0,
|
||||
shortestDiameter: 0,
|
||||
isDeleted: false,
|
||||
isTarget: true
|
||||
isTarget: true,
|
||||
measurementType: 'bidirectional'
|
||||
};
|
||||
return measurementData;
|
||||
}
|
||||
|
||||
@ -153,7 +153,8 @@
|
||||
studyInstanceUid: studyInstanceUid,
|
||||
patientId: patientId,
|
||||
response: '',
|
||||
isTarget: false
|
||||
isTarget: false,
|
||||
measurementType: 'nonTarget'
|
||||
};
|
||||
|
||||
return measurementData;
|
||||
|
||||
@ -1,7 +1,14 @@
|
||||
Template.lesionTable.helpers({
|
||||
measurement: function() {
|
||||
// All Targets shall be listed first followed by Non-Targets
|
||||
return Measurements.find({}, {
|
||||
return Measurements.find({
|
||||
measurementType: {
|
||||
$in: [
|
||||
'bidirectional',
|
||||
'nonTarget'
|
||||
]
|
||||
}
|
||||
}, {
|
||||
sort: {
|
||||
isTarget: -1,
|
||||
lesionNumberAbsolute: 1
|
||||
|
||||
@ -1,3 +1,17 @@
|
||||
measurementValuesByType = {
|
||||
bidirectional: [
|
||||
'shortestDiameter',
|
||||
'longestDiameter'
|
||||
],
|
||||
nonTarget: ['response'],
|
||||
length: ['length'],
|
||||
ellipticalRoi: [
|
||||
'area',
|
||||
'mean',
|
||||
'stdev'
|
||||
]
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the Timepoint object for a specific Measurement.
|
||||
* If no measurement exists yet, one will be created.
|
||||
@ -46,13 +60,18 @@ function updateLesionData(lesionData) {
|
||||
imageId: lesionData.imageId
|
||||
};
|
||||
|
||||
if (lesionData.isTarget === true) {
|
||||
timepointData.shortestDiameter = lesionData.shortestDiameter;
|
||||
timepointData.longestDiameter = lesionData.longestDiameter;
|
||||
} else {
|
||||
timepointData.response = lesionData.response;
|
||||
if (!lesionData.measurementType) {
|
||||
// For debugging
|
||||
log.warn('No MeasurementType available?');
|
||||
}
|
||||
|
||||
// Populate this timepoint's data with whichever values
|
||||
// are stored for this Measurement type
|
||||
var values = measurementValuesByType[lesionData.measurementType];
|
||||
values.forEach(function(valueName) {
|
||||
timepointData[valueName] = lesionData[valueName];
|
||||
});
|
||||
|
||||
// If no such lesion exists, we need to add one
|
||||
if (!existingMeasurement) {
|
||||
// Create a data structure for the Measurement
|
||||
|
||||
@ -233,7 +233,6 @@ function validateSingleMeasurement(measurementData) {
|
||||
|
||||
// If no such Measurement exists, stop here
|
||||
if (!measurement) {
|
||||
log.warn('No Measurement found?');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
clearTools = function() {
|
||||
var patientId = Session.get('patientId');
|
||||
var toolTypes = [ 'lesion', 'nonTarget', 'length' ];
|
||||
var toolTypes = [ 'lesion', 'nonTarget', 'length', 'ellipticalRoi'];
|
||||
var toolState = cornerstoneTools.globalImageIdSpecificToolStateManager.toolState;
|
||||
var toolStateKeys = Object.keys(toolState).slice(0);
|
||||
|
||||
@ -43,6 +43,7 @@ clearTools = function() {
|
||||
|
||||
// Remove patient's measurements
|
||||
Meteor.call('removeMeasurementsByPatientId', patientId);
|
||||
Meteor.call('removeImageMeasurementsByPatientId', patientId);
|
||||
|
||||
// Clear all validation errors
|
||||
ValidationErrors.remove({});
|
||||
|
||||
@ -7,6 +7,27 @@ handleMeasurementAdded = function(e, eventData) {
|
||||
log.info('CornerstoneToolsMeasurementAdded');
|
||||
LesionManager.updateLesionData(measurementData);
|
||||
TrialResponseCriteria.validateDelayed(measurementData);
|
||||
break;
|
||||
case 'ellipticalRoi':
|
||||
case 'length':
|
||||
var enabledElement = cornerstone.getEnabledElement(eventData.element);
|
||||
var imageId = enabledElement.image.imageId;
|
||||
|
||||
// Get the studyInstanceUid and series metaData
|
||||
var study = cornerstoneTools.metaData.get('study', imageId);
|
||||
var series = cornerstoneTools.metaData.get('series', imageId);
|
||||
|
||||
// Add the relevant metaData to this ImageMeasurement's toolData
|
||||
measurementData.clientId = ClientId;
|
||||
measurementData.imageId = imageId;
|
||||
measurementData.measurementType = eventData.toolType;
|
||||
measurementData.patientId = study.patientId;
|
||||
measurementData.studyInstanceUid = study.studyInstanceUid;
|
||||
measurementData.seriesInstanceUid = series.seriesInstanceUid;
|
||||
|
||||
// Create the ImageMeasurement in the database
|
||||
measurementData._id = ImageMeasurements.insert(measurementData);
|
||||
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
@ -8,5 +8,31 @@ handleMeasurementModified = function(e, eventData) {
|
||||
LesionManager.updateLesionData(measurementData);
|
||||
TrialResponseCriteria.validateDelayed(measurementData);
|
||||
break;
|
||||
case 'ellipticalRoi':
|
||||
case 'length':
|
||||
var enabledElement = cornerstone.getEnabledElement(eventData.element);
|
||||
var imageId = enabledElement.image.imageId;
|
||||
|
||||
// Get the studyInstanceUid and series metaData
|
||||
var study = cornerstoneTools.metaData.get('study', imageId);
|
||||
var series = cornerstoneTools.metaData.get('series', imageId);
|
||||
|
||||
// Add the relevant metaData to this ImageMeasurement's toolData
|
||||
measurementData.clientId = ClientId;
|
||||
measurementData.imageId = imageId;
|
||||
measurementData.measurementType = eventData.toolType;
|
||||
measurementData.patientId = study.patientId;
|
||||
measurementData.studyInstanceUid = study.studyInstanceUid;
|
||||
measurementData.seriesInstanceUid = series.seriesInstanceUid;
|
||||
var toUpdate = $.extend({}, measurementData);
|
||||
|
||||
// Remove the Mongo _id otherwise it will complain when we try to 'mod' it.
|
||||
delete toUpdate._id;
|
||||
|
||||
// Update the ImageMeasurement in the database
|
||||
ImageMeasurements.update(measurementData._id, {
|
||||
$set: toUpdate
|
||||
});
|
||||
break;
|
||||
}
|
||||
};
|
||||
@ -15,13 +15,14 @@ removeToolDataWithMeasurementId = function(imageId, toolType, measurementId) {
|
||||
// Search toolData for entries linked to the specified Measurement
|
||||
var toRemove = [];
|
||||
toolData.forEach(function(measurement, index) {
|
||||
if (measurement.id === measurementId) {
|
||||
if (measurement.id === measurementId ||
|
||||
measurement._id === measurementId) {
|
||||
toRemove.push(index);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
log.info("Removing Indices: ");
|
||||
log.info('Removing Indices: ');
|
||||
log.info(toRemove);
|
||||
|
||||
// If any toolData entries need to be removed, splice them from
|
||||
|
||||
@ -0,0 +1,59 @@
|
||||
syncImageMeasurementAndToolData = function(measurement) {
|
||||
// TODO: Refactor this to merge it with syncMeasurementAndToolData somehow
|
||||
log.info('syncImageMeasurementAndToolData');
|
||||
|
||||
var toolState = cornerstoneTools.globalImageIdSpecificToolStateManager.toolState;
|
||||
|
||||
var imageId = measurement.imageId;
|
||||
var toolType = measurement.measurementType;
|
||||
|
||||
// If no tool state exists for this imageId, create an empty object to store it
|
||||
if (!toolState[imageId]) {
|
||||
toolState[imageId] = {};
|
||||
}
|
||||
|
||||
// Check if we already have toolData for this imageId and toolType
|
||||
if (toolState[imageId][toolType] &&
|
||||
toolState[imageId][toolType].data &&
|
||||
toolState[imageId][toolType].data.length) {
|
||||
// If we have toolData, we should search it for any toolData
|
||||
// related to the current ImageMeasurement
|
||||
var toolData = toolState[imageId][toolType].data;
|
||||
|
||||
// Create a flag so we know if we have successfully updated
|
||||
// this ImageMeasurement's timepoint data in the toolData
|
||||
var alreadyExists = false;
|
||||
|
||||
// Loop through the toolData to search for this ImageMeasurement
|
||||
toolData.forEach(function(tool) {
|
||||
// Break the loop if this isn't the ImageMeasurement we are looking for
|
||||
if (tool._id !== measurement._id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we find the ImageMeasurement, set the flag to True
|
||||
alreadyExists = true;
|
||||
|
||||
// Update the toolData from the ImageMeasurement data
|
||||
$.extend(tool, measurement);
|
||||
return false;
|
||||
});
|
||||
|
||||
// If we found the ImageMeasurement we intended to update, we can stop
|
||||
// this function here
|
||||
if (alreadyExists === true) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// If no toolData exists for this toolType, create an empty array to hold some
|
||||
toolState[imageId][toolType] = {
|
||||
data: []
|
||||
};
|
||||
}
|
||||
|
||||
// If we have reached this point, it means we haven't found the ImageMeasurement we are
|
||||
// looking for in the current toolData. This means we need to add it.
|
||||
|
||||
// Add the ImageMeasurementData into the toolData for this imageId
|
||||
toolState[imageId][toolType].data.push(measurement);
|
||||
};
|
||||
@ -146,6 +146,7 @@ Package.onUse(function(api) {
|
||||
api.addFiles('lib/calculateTotalLesionBurden.js', 'client');
|
||||
|
||||
api.addFiles('lib/syncMeasurementAndToolData.js', 'client');
|
||||
api.addFiles('lib/syncImageMeasurementAndToolData.js', 'client');
|
||||
api.addFiles('lib/updateRelatedElements.js', 'client');
|
||||
|
||||
api.addFiles('lib/handleMeasurementAdded.js', 'client');
|
||||
@ -159,6 +160,7 @@ Package.onUse(function(api) {
|
||||
api.export('handleMeasurementModified', 'client');
|
||||
api.export('handleMeasurementRemoved', 'client');
|
||||
api.export('syncMeasurementAndToolData', 'client');
|
||||
api.export('syncImageMeasurementAndToolData', 'client');
|
||||
api.export('updateRelatedElements', 'client');
|
||||
api.export('openNewTabWithTimepoint', 'client');
|
||||
api.export('activateLesion', 'client');
|
||||
@ -184,6 +186,7 @@ Package.onUse(function(api) {
|
||||
api.export('LocationResponses', 'client');
|
||||
|
||||
// Export collections spanning both client and server
|
||||
api.export('ImageMeasurements', [ 'client', 'server' ]);
|
||||
api.export('Measurements', [ 'client', 'server' ]);
|
||||
api.export('Studies', [ 'client', 'server' ]);
|
||||
api.export('Timepoints', [ 'client', 'server' ]);
|
||||
|
||||
@ -24,6 +24,12 @@ Meteor.publish('singlePatientMeasurements', function(patientId) {
|
||||
});
|
||||
});
|
||||
|
||||
Meteor.publish('singlePatientImageMeasurements', function(patientId) {
|
||||
return ImageMeasurements.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() {
|
||||
|
||||
@ -44,6 +44,11 @@ Meteor.methods({
|
||||
patientId: patientId
|
||||
});
|
||||
},
|
||||
removeImageMeasurementsByPatientId: function(patientId) {
|
||||
ImageMeasurements.remove({
|
||||
patientId: patientId
|
||||
});
|
||||
},
|
||||
clearAllTimepoints: function() {
|
||||
Timepoints.remove({});
|
||||
},
|
||||
|
||||
6
Packages/viewerbase/lib/updateAllViewports.js
Normal file
6
Packages/viewerbase/lib/updateAllViewports.js
Normal file
@ -0,0 +1,6 @@
|
||||
updateAllViewports = function() {
|
||||
var viewports = $('.imageViewerViewport').not('.empty');
|
||||
viewports.each(function(index, element) {
|
||||
cornerstone.updateImage(element);
|
||||
});
|
||||
};
|
||||
@ -1,5 +1,5 @@
|
||||
getActiveViewportElement = function() {
|
||||
var viewportIndex = Session.get("activeViewport");
|
||||
var viewportIndex = Session.get("activeViewport") || 0;
|
||||
return $('.imageViewerViewport').get(viewportIndex);
|
||||
};
|
||||
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
Package.describe({
|
||||
name: "viewerbase",
|
||||
summary: "Shared components and functions for Meteor DICOM Viewers",
|
||||
version: '0.0.1'
|
||||
name: 'viewerbase',
|
||||
summary: 'Shared components and functions for Meteor DICOM Viewers',
|
||||
version: '0.0.1'
|
||||
});
|
||||
|
||||
Package.onUse(function (api) {
|
||||
Package.onUse(function(api) {
|
||||
api.versionsFrom('1.2.0.2');
|
||||
|
||||
api.use('standard-app-packages');
|
||||
@ -27,7 +27,6 @@ Package.onUse(function (api) {
|
||||
// TODO= Find a meteor package for this
|
||||
api.addFiles('client/compatibility/jquery.hotkeys.js', 'client');
|
||||
|
||||
|
||||
// ---------- Collections ----------
|
||||
api.addFiles('client/collections.js', 'client');
|
||||
|
||||
@ -128,12 +127,12 @@ Package.onUse(function (api) {
|
||||
api.addFiles('lib/WLPresets.js', 'client');
|
||||
api.addFiles('lib/resizeViewportElements.js', 'client');
|
||||
api.addFiles('lib/setFocusToActiveViewport.js', 'client');
|
||||
api.addFiles('lib/updateAllViewports.js', 'client');
|
||||
api.addFiles('lib/encodeQueryData.js', 'server');
|
||||
|
||||
|
||||
//api.export('accountsConfig', 'client');
|
||||
api.export('resizeViewportElements','client');
|
||||
api.export('handleResize','client');
|
||||
api.export('resizeViewportElements', 'client');
|
||||
api.export('handleResize', 'client');
|
||||
api.export('enableHotkeys', 'client');
|
||||
api.export('enablePrefetchOnElement', 'client');
|
||||
api.export('displayReferenceLines', 'client');
|
||||
@ -146,6 +145,7 @@ Package.onUse(function (api) {
|
||||
api.export('sortStudy', 'client');
|
||||
api.export('updateOrientationMarkers', 'client');
|
||||
api.export('setFocusToActiveViewport', 'client');
|
||||
api.export('updateAllViewports', 'client');
|
||||
api.export('encodeQueryData', 'server');
|
||||
|
||||
// Viewer management objects
|
||||
|
||||
Loading…
Reference in New Issue
Block a user