Updates to save viewport data across sessions / while switching tabs
This commit is contained in:
parent
56417df385
commit
4721400b34
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
.meteor/local
|
||||
.meteor/meteorite
|
||||
node_modules
|
||||
5
LesionTracker/client/body.styl
Normal file
5
LesionTracker/client/body.styl
Normal file
@ -0,0 +1,5 @@
|
||||
body
|
||||
background-color: #202020
|
||||
|
||||
#search-panel-head
|
||||
background-color: #8845AB
|
||||
@ -7,7 +7,7 @@
|
||||
font-family: "Sanchez"
|
||||
color: #C1C1C1
|
||||
|
||||
.navbar-form, .navbar-default .navbar-nav > li > a
|
||||
.navbar-default .navbar-nav > li > a
|
||||
color: #C1C1C1
|
||||
font-weight: bold
|
||||
font-size: 12pt
|
||||
|
||||
@ -1,16 +1,30 @@
|
||||
OHIF = {
|
||||
viewer: {}
|
||||
};
|
||||
function resizeViewports() {
|
||||
// Handle resizing of image viewer viewports
|
||||
// For some reason, this seems to need to be on
|
||||
// another delay, or the resizing won't work properly
|
||||
viewportResizeTimer = setTimeout(function() {
|
||||
var elements = $('.imageViewerViewport');
|
||||
elements.each(function(index) {
|
||||
var element = this;
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
cornerstone.resize(element, true);
|
||||
});
|
||||
}, 1);
|
||||
}
|
||||
|
||||
OHIF.viewer.imageViewerLoadedSeriesDictionary = {};
|
||||
OHIF.viewer.imageViewerCurrentImageIdIndexDictionary = {};
|
||||
OHIF.viewer.loadIndicatorDelay = 3000;
|
||||
OHIF.viewer.defaultTool = 'wwwc';
|
||||
OHIF.viewer.refLinesEnabled = true;
|
||||
OHIF.viewer.isPlaying = {};
|
||||
Template.viewer.onCreated(function() {
|
||||
console.log("Image Viewer onCreated");
|
||||
OHIF = {
|
||||
viewer: {}
|
||||
};
|
||||
|
||||
OHIF.viewer.loadIndicatorDelay = 3000;
|
||||
OHIF.viewer.defaultTool = 'wwwc';
|
||||
OHIF.viewer.refLinesEnabled = true;
|
||||
OHIF.viewer.isPlaying = {};
|
||||
|
||||
Meteor.startup(function() {
|
||||
OHIF.viewer.updateImageSynchronizer = new cornerstoneTools.Synchronizer("CornerstoneNewImage", cornerstoneTools.updateImageSynchronizer);
|
||||
OHIF.viewer.functionList = {
|
||||
invert: function(element) {
|
||||
var viewport = cornerstone.getViewport(element);
|
||||
@ -40,25 +54,7 @@ Meteor.startup(function() {
|
||||
trigger: 'hover'
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
function resizeViewports() {
|
||||
// Handle resizing of image viewer viewports
|
||||
// For some reason, this seems to need to be on
|
||||
// another delay, or the resizing won't work properly
|
||||
viewportResizeTimer = setTimeout(function() {
|
||||
var elements = $('.imageViewerViewport');
|
||||
elements.each(function(index) {
|
||||
var element = this;
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
cornerstone.resize(element, true);
|
||||
});
|
||||
}, 1);
|
||||
}
|
||||
|
||||
Template.viewer.onCreated(function() {
|
||||
if (this.data.activeViewport === undefined) {
|
||||
this.data.activeViewport = new ReactiveVar(0);
|
||||
}
|
||||
@ -68,6 +64,27 @@ Template.viewer.onCreated(function() {
|
||||
if (this.data.viewportColumns === undefined) {
|
||||
this.data.viewportColumns = new ReactiveVar(2);
|
||||
}
|
||||
|
||||
var contentId = this.data.contentId;
|
||||
|
||||
// Update the viewer data object
|
||||
ViewerData[contentId].viewportColumns = this.data.viewportColumns;
|
||||
ViewerData[contentId].viewportRows = this.data.viewportRows;
|
||||
ViewerData[contentId].activeViewport = this.data.activeViewport;
|
||||
Session.set('ViewerData', ViewerData);
|
||||
|
||||
if (ViewerData[contentId].viewer) {
|
||||
OHIF.viewer = ViewerData[contentId].viewer;
|
||||
} else {
|
||||
OHIF.viewer.imageViewerLoadedSeriesDictionary = {};
|
||||
ViewerData[contentId].viewer = OHIF.viewer;
|
||||
}
|
||||
|
||||
OHIF.viewer.updateImageSynchronizer = new cornerstoneTools.Synchronizer("CornerstoneNewImage", cornerstoneTools.updateImageSynchronizer);
|
||||
});
|
||||
|
||||
Template.viewer.onDestroyed(function() {
|
||||
OHIF.viewer.updateImageSynchronizer.destroy();
|
||||
});
|
||||
|
||||
// Avoid doing DOM manipulation during the resize handler
|
||||
|
||||
@ -9,9 +9,21 @@ Router.route('/', function () {
|
||||
});
|
||||
|
||||
tabs = new Meteor.Collection(null);
|
||||
Session.setDefault('ViewerData', {});
|
||||
Session.setDefault('ViewerDataUpdated', Random.id());
|
||||
|
||||
Object.keys(ViewerData).forEach(function(contentId) {
|
||||
var tabData = ViewerData[contentId];
|
||||
var data = {
|
||||
title: tabData.title,
|
||||
contentid: tabData.contentId,
|
||||
};
|
||||
tabs.insert(data);
|
||||
});
|
||||
|
||||
|
||||
Router.route('/viewer/:_id', {
|
||||
layoutTemplate: 'layoutLesionTracker',
|
||||
layoutTemplate: 'layout',
|
||||
name: 'viewer',
|
||||
onBeforeAction: function() {
|
||||
var self = this;
|
||||
@ -19,21 +31,19 @@ Router.route('/viewer/:_id', {
|
||||
Meteor.call('GetStudyMetadata', this.params._id, function(error, study) {
|
||||
sortStudy(study);
|
||||
|
||||
var data = {
|
||||
studies: [study]
|
||||
};
|
||||
var title = study.seriesList[0].instances[0].patientName;
|
||||
var contentid = generateUUID();
|
||||
|
||||
var newTabObject = {
|
||||
var data = {
|
||||
title: title,
|
||||
contentid: contentid,
|
||||
};
|
||||
tabs.insert(newTabObject);
|
||||
tabs.insert(data);
|
||||
|
||||
data.studies = [study];
|
||||
|
||||
self.render('worklist');
|
||||
Session.set('DataInTab#' + contentid, data);
|
||||
Session.set('OpenNewTabEvent', contentid);
|
||||
Session.set('OpenNewTabEvent', data);
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -1,16 +1,30 @@
|
||||
OHIF = {
|
||||
viewer: {}
|
||||
};
|
||||
function resizeViewports() {
|
||||
// Handle resizing of image viewer viewports
|
||||
// For some reason, this seems to need to be on
|
||||
// another delay, or the resizing won't work properly
|
||||
viewportResizeTimer = setTimeout(function() {
|
||||
var elements = $('.imageViewerViewport');
|
||||
elements.each(function(index) {
|
||||
var element = this;
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
cornerstone.resize(element, true);
|
||||
});
|
||||
}, 1);
|
||||
}
|
||||
|
||||
OHIF.viewer.imageViewerLoadedSeriesDictionary = {};
|
||||
OHIF.viewer.imageViewerCurrentImageIdIndexDictionary = {};
|
||||
OHIF.viewer.loadIndicatorDelay = 3000;
|
||||
OHIF.viewer.defaultTool = 'wwwc';
|
||||
OHIF.viewer.refLinesEnabled = true;
|
||||
OHIF.viewer.isPlaying = {};
|
||||
Template.viewer.onCreated(function() {
|
||||
console.log("Image Viewer onCreated");
|
||||
OHIF = {
|
||||
viewer: {}
|
||||
};
|
||||
|
||||
OHIF.viewer.loadIndicatorDelay = 3000;
|
||||
OHIF.viewer.defaultTool = 'wwwc';
|
||||
OHIF.viewer.refLinesEnabled = true;
|
||||
OHIF.viewer.isPlaying = {};
|
||||
|
||||
Meteor.startup(function() {
|
||||
OHIF.viewer.updateImageSynchronizer = new cornerstoneTools.Synchronizer("CornerstoneNewImage", cornerstoneTools.updateImageSynchronizer);
|
||||
OHIF.viewer.functionList = {
|
||||
invert: function(element) {
|
||||
var viewport = cornerstone.getViewport(element);
|
||||
@ -40,25 +54,7 @@ Meteor.startup(function() {
|
||||
trigger: 'hover'
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
function resizeViewports() {
|
||||
// Handle resizing of image viewer viewports
|
||||
// For some reason, this seems to need to be on
|
||||
// another delay, or the resizing won't work properly
|
||||
viewportResizeTimer = setTimeout(function() {
|
||||
var elements = $('.imageViewerViewport');
|
||||
elements.each(function(index) {
|
||||
var element = this;
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
cornerstone.resize(element, true);
|
||||
});
|
||||
}, 1);
|
||||
}
|
||||
|
||||
Template.viewer.onCreated(function() {
|
||||
if (this.data.activeViewport === undefined) {
|
||||
this.data.activeViewport = new ReactiveVar(0);
|
||||
}
|
||||
@ -69,13 +65,26 @@ Template.viewer.onCreated(function() {
|
||||
this.data.viewportColumns = new ReactiveVar(1);
|
||||
}
|
||||
|
||||
var viewportColumns = this.data.viewportColumns.get();
|
||||
var viewportRows = this.data.viewportRows.get();
|
||||
var activeViewport = this.data.activeViewport.get();
|
||||
console.log('Reactive changes!');
|
||||
console.log('viewportColumns: ' + viewportColumns);
|
||||
console.log('viewportRows: ' + viewportRows);
|
||||
console.log('activeViewport: ' + activeViewport);
|
||||
var contentId = this.data.contentId;
|
||||
|
||||
// Update the viewer data object
|
||||
ViewerData[contentId].viewportColumns = this.data.viewportColumns;
|
||||
ViewerData[contentId].viewportRows = this.data.viewportRows;
|
||||
ViewerData[contentId].activeViewport = this.data.activeViewport;
|
||||
Session.set('ViewerData', ViewerData);
|
||||
|
||||
if (ViewerData[contentId].viewer) {
|
||||
OHIF.viewer = ViewerData[contentId].viewer;
|
||||
} else {
|
||||
OHIF.viewer.imageViewerLoadedSeriesDictionary = {};
|
||||
ViewerData[contentId].viewer = OHIF.viewer;
|
||||
}
|
||||
|
||||
OHIF.viewer.updateImageSynchronizer = new cornerstoneTools.Synchronizer("CornerstoneNewImage", cornerstoneTools.updateImageSynchronizer);
|
||||
});
|
||||
|
||||
Template.viewer.onDestroyed(function() {
|
||||
OHIF.viewer.updateImageSynchronizer.destroy();
|
||||
});
|
||||
|
||||
// Avoid doing DOM manipulation during the resize handler
|
||||
|
||||
@ -4,13 +4,23 @@ Router.configure({
|
||||
notFoundTemplate: 'notFound'
|
||||
});
|
||||
|
||||
|
||||
|
||||
Router.route('/', function () {
|
||||
this.render('worklist', {});
|
||||
});
|
||||
|
||||
tabs = new Meteor.Collection(null);
|
||||
Session.setDefault('ViewerData', {});
|
||||
Session.setDefault('ViewerDataUpdated', Random.id());
|
||||
|
||||
Object.keys(ViewerData).forEach(function(contentId) {
|
||||
var tabData = ViewerData[contentId];
|
||||
var data = {
|
||||
title: tabData.title,
|
||||
contentid: tabData.contentId,
|
||||
};
|
||||
tabs.insert(data);
|
||||
});
|
||||
|
||||
|
||||
Router.route('/viewer/:_id', {
|
||||
layoutTemplate: 'layout',
|
||||
@ -21,21 +31,19 @@ Router.route('/viewer/:_id', {
|
||||
Meteor.call('GetStudyMetadata', this.params._id, function(error, study) {
|
||||
sortStudy(study);
|
||||
|
||||
var data = {
|
||||
studies: [study]
|
||||
};
|
||||
var title = study.seriesList[0].instances[0].patientName;
|
||||
var contentid = generateUUID();
|
||||
|
||||
var newTabObject = {
|
||||
var data = {
|
||||
title: title,
|
||||
contentid: contentid,
|
||||
};
|
||||
tabs.insert(newTabObject);
|
||||
tabs.insert(data);
|
||||
|
||||
data.studies = [study];
|
||||
|
||||
self.render('worklist');
|
||||
Session.set('DataInTab#' + contentid, data);
|
||||
Session.set('OpenNewTabEvent', contentid);
|
||||
Session.set('OpenNewTabEvent', data);
|
||||
});
|
||||
}
|
||||
});
|
||||
45
Packages/cornerstone/client/hangingProtocol.js
Normal file
45
Packages/cornerstone/client/hangingProtocol.js
Normal file
@ -0,0 +1,45 @@
|
||||
var hangingProtocol;
|
||||
|
||||
function defaultHangingProtocol(inputData) {
|
||||
var studies = inputData.studies;
|
||||
var viewportRows = inputData.viewportRows;
|
||||
var viewportColumns = inputData.viewportColumns;
|
||||
|
||||
// This is the most basic hanging protocol.
|
||||
var stacks = [];
|
||||
studies.forEach(function(study) {
|
||||
study.seriesList.forEach(function(series) {
|
||||
var stack = {
|
||||
series: series,
|
||||
study: study
|
||||
};
|
||||
stacks.push(stack);
|
||||
});
|
||||
});
|
||||
|
||||
var viewportData = [];
|
||||
|
||||
var numViewports = viewportRows * viewportColumns;
|
||||
for (var i=0; i < numViewports; ++i) {
|
||||
if (i >= stacks.length) {
|
||||
// We don't have enough stacks to fill the desired number of viewports, so stop here
|
||||
break;
|
||||
}
|
||||
viewportData[i] = {
|
||||
seriesInstanceUid: stacks[i].series.seriesInstanceUid,
|
||||
studyInstanceUid: stacks[i].study.studyInstanceUid,
|
||||
currentImageIdIndex: 0
|
||||
};
|
||||
}
|
||||
return viewportData;
|
||||
}
|
||||
|
||||
getHangingProtocol = function() {
|
||||
return hangingProtocol;
|
||||
};
|
||||
|
||||
setHangingProtocol = function(protocol) {
|
||||
hangingProtocol = protocol;
|
||||
};
|
||||
|
||||
setHangingProtocol(defaultHangingProtocol);
|
||||
77
Packages/cornerstone/client/measurementManager.js
Normal file
77
Packages/cornerstone/client/measurementManager.js
Normal file
@ -0,0 +1,77 @@
|
||||
(function($, cornerstoneTools) {
|
||||
|
||||
'use strict';
|
||||
|
||||
// This object manages a collection of measurements
|
||||
function MeasurementManager() {
|
||||
// Define some basic validator functions (everything is always valid)
|
||||
this.singleMeasurementValidator = function() {
|
||||
return {isValid: true};
|
||||
};
|
||||
|
||||
this.measurementSetValidator = function() {
|
||||
return {isValid: true};
|
||||
};
|
||||
|
||||
var that = this;
|
||||
that.measurements = [];
|
||||
|
||||
// adds an element as both a source and a target
|
||||
this.add = function(measurement, validateSingle, validateSet) {
|
||||
var index = that.measurements.push(measurement);
|
||||
|
||||
if (validateSingle === true) {
|
||||
measurementValid = singleMeasurementValidator(measurement);
|
||||
}
|
||||
|
||||
if (validateSet === true) {
|
||||
measurementSetValid = measurementSetValidator(measurement);
|
||||
}
|
||||
|
||||
// fire event
|
||||
var eventDetail = {
|
||||
index: index,
|
||||
measurement: measurement,
|
||||
measurementValid: measurementValid,
|
||||
measurementSetValid: measurementSetValid
|
||||
};
|
||||
|
||||
$(that).trigger('CornerstoneMeasurementAdded', eventDetail);
|
||||
};
|
||||
|
||||
this.remove = function(index) {
|
||||
var measurement = that.measurements[index];
|
||||
that.measurements.splice(index, 1);
|
||||
// fire event
|
||||
var eventDetail = {
|
||||
index: index,
|
||||
measurement: measurement
|
||||
};
|
||||
$(that).trigger('CornerstoneMeasurementRemoved', eventDetail);
|
||||
};
|
||||
|
||||
// Add set/get measurementValidator to run on each single measurement
|
||||
// (e.g. length > 10 mm, location must be 30 mm from lymph node)
|
||||
this.setSingleMeasurementValidator = function(validator) {
|
||||
this.singleMeasurementValidator = validator;
|
||||
};
|
||||
|
||||
this.getSingleMeasurementValidator = function(validator) {
|
||||
return singleMeasurementValidator;
|
||||
};
|
||||
|
||||
// Add set/get groupValidator to run on all of the measurements together
|
||||
// (e.g. the number of measurments must be <= 2)
|
||||
this.setMeasurementSetValidator = function(validator) {
|
||||
this.measurementSetValidator = validator;
|
||||
};
|
||||
|
||||
this.getMeasurementSetValidator = function(validator) {
|
||||
return measurementSetValidator;
|
||||
};
|
||||
}
|
||||
|
||||
// module/private exports
|
||||
cornerstoneTools.MeasurementManager = new MeasurementManager();
|
||||
|
||||
})($, cornerstoneTools);
|
||||
57
Packages/cornerstone/client/measurementManagerExample.js
Normal file
57
Packages/cornerstone/client/measurementManagerExample.js
Normal file
@ -0,0 +1,57 @@
|
||||
// Example usage for something like RECIST
|
||||
var tumourLengthMeasurementManager = cornerstoneTools.MeasurementManager;
|
||||
function singleValidator(measurement) {
|
||||
var result = {
|
||||
isValid: true
|
||||
};
|
||||
if (measurement.distance < 10) {
|
||||
result.isValid = false;
|
||||
result.details = 'Measurement distance must be less than 10 mm';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function setValidator(measurements) {
|
||||
var result = {
|
||||
isValid: true
|
||||
};
|
||||
if (measurements.length > 2) {
|
||||
result.isValid = false;
|
||||
result.details = 'The number of measurements per image must be less than 2';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Or a remote call
|
||||
function remoteSetValidator(measurements) {
|
||||
var deferred = $.Deferred;
|
||||
|
||||
var result = {
|
||||
isValid: true,
|
||||
promise: deferred.promise()
|
||||
};
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState == 4) {
|
||||
if (xhr.status == 200) {
|
||||
var response = JSON.parse(xhr.response);
|
||||
if (response.isValid) {
|
||||
deferred.resolve(xhr.responseText);
|
||||
} else {
|
||||
deferred.reject(xhr.responseText);
|
||||
}
|
||||
} else {
|
||||
// The request didn't succeed
|
||||
deferred.reject(xhr.responseText);
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.open("POST", "/urlToValidateRECIST", true);
|
||||
xhr.send(measurements);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
tumourLengthMeasurementManager.setSingleMeasurementValidator(singleValidator);
|
||||
tumourLengthMeasurementManager.setMeasurementSetValidator(setValidator);
|
||||
@ -17,11 +17,18 @@ Package.onUse(function (api) {
|
||||
api.addFiles('client/dicomParser.js', 'client', {bare: true});
|
||||
api.addFiles('client/hammer.js', 'client', {bare: true});
|
||||
|
||||
api.addFiles('client/hangingProtocol.js', 'client', {bare: true});
|
||||
api.addFiles('client/measurementManager.js', 'client', {bare: true});
|
||||
api.addFiles('client/measurementManagerExample.js', 'client', {bare: true});
|
||||
|
||||
api.export("cornerstone", 'client');
|
||||
api.export("cornerstoneMath", 'client');
|
||||
api.export("cornerstoneTools", 'client');
|
||||
api.export("cornerstoneWADOImageLoader", 'client');
|
||||
api.export("cornerstoneWADORSImageLoader", 'client');
|
||||
api.export("dicomParser", 'client');
|
||||
|
||||
api.export("getHangingProtocol", 'client');
|
||||
api.export("setHangingProtocol", 'client');
|
||||
});
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template name="layout">
|
||||
<nav class="navbar navbar-default" role="navigation">
|
||||
<nav class="navbar navbar-default noselect" role="navigation">
|
||||
<div class="container-fluid">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
|
||||
|
||||
@ -1,3 +1,11 @@
|
||||
.noselect
|
||||
-webkit-touch-callout: none
|
||||
-webkit-user-select: none
|
||||
-khtml-user-select: none
|
||||
-moz-user-select: none
|
||||
-ms-user-select: none
|
||||
user-select: none
|
||||
|
||||
.navbar
|
||||
margin-bottom: 0
|
||||
border-radius: 0
|
||||
|
||||
@ -28,7 +28,15 @@ function displayReferenceLines(viewportIndex) {
|
||||
var element = $('.imageViewerViewport').get(viewportIndex);
|
||||
|
||||
$('.imageViewerViewport').each(function(index, element) {
|
||||
if (!$(this).find('canvas').length) {
|
||||
var imageId;
|
||||
try {
|
||||
var enabledElement = cornerstone.getEnabledElement(element);
|
||||
imageId = enabledElement.image.imageId;
|
||||
} catch(error) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!imageId || !$(this).find('canvas').length) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -41,14 +49,16 @@ function displayReferenceLines(viewportIndex) {
|
||||
}
|
||||
|
||||
function loadSeriesIntoViewport(data) {
|
||||
if (!data.series || !data.viewport) {
|
||||
if (!data.series || !data.element) {
|
||||
return;
|
||||
}
|
||||
|
||||
var contentId = $("#viewer").parents('.tab-pane.active').attr('id');
|
||||
|
||||
var study = data.study;
|
||||
var series = data.series;
|
||||
var element = data.viewport;
|
||||
var viewportIndex = $(".imageViewerViewport").index(data.viewport);
|
||||
var element = data.element;
|
||||
var viewportIndex = $(".imageViewerViewport").index(element);
|
||||
|
||||
var allEvents = 'CornerstoneToolsMouseDown CornerstoneToolsMouseDownActivate ' +
|
||||
'CornerstoneToolsMouseClick CornerstoneToolsMouseDrag CornerstoneToolsMouseUp ' +
|
||||
@ -72,7 +82,7 @@ function loadSeriesIntoViewport(data) {
|
||||
});
|
||||
|
||||
var stack = {
|
||||
currentImageIdIndex: 0,
|
||||
currentImageIdIndex: data.currentImageIdIndex || 0,
|
||||
imageIds: imageIds
|
||||
};
|
||||
|
||||
@ -86,13 +96,28 @@ function loadSeriesIntoViewport(data) {
|
||||
// If you have problems, replace it with this line instead:
|
||||
// cornerstone.enable(element);
|
||||
|
||||
var startLoadingHandler = cornerstoneTools.loadHandlerManager.getStartLoadHandler();
|
||||
var endLoadingHandler = cornerstoneTools.loadHandlerManager.getEndLoadHandler();
|
||||
var errorLoadingHandler = cornerstoneTools.loadHandlerManager.getErrorLoadingHandler();
|
||||
|
||||
if (startLoadingHandler) {
|
||||
startLoadingHandler(element);
|
||||
}
|
||||
|
||||
cornerstone.loadAndCacheImage(imageId).then(function(image) {
|
||||
cornerstone.displayImage(element, image);
|
||||
if (data.viewport) {
|
||||
cornerstone.displayImage(element, image, data.viewport);
|
||||
} else {
|
||||
cornerstone.displayImage(element, image);
|
||||
}
|
||||
|
||||
if (endLoadingHandler) {
|
||||
endLoadingHandler(element);
|
||||
}
|
||||
|
||||
element.classList.remove('empty');
|
||||
$(element).siblings('.viewportInstructions').hide();
|
||||
$(element).siblings('.imageViewerViewportOverlay').show();
|
||||
|
||||
cornerstone.resize(element, true);
|
||||
|
||||
var imagePlane = cornerstoneTools.metaData.get('imagePlane', image.imageId);
|
||||
|
||||
@ -118,6 +143,9 @@ function loadSeriesIntoViewport(data) {
|
||||
|
||||
function onImageRendered(e, eventData) {
|
||||
Session.set('CornerstoneImageRendered' + viewportIndex, Random.id());
|
||||
var viewport = cornerstone.getViewport(element);
|
||||
ViewerData[contentId].viewer.imageViewerLoadedSeriesDictionary[viewportIndex].viewport = viewport;
|
||||
Session.set('ViewerData', ViewerData);
|
||||
}
|
||||
|
||||
$(element).off('CornerstoneImageRendered', onImageRendered);
|
||||
@ -129,13 +157,20 @@ function loadSeriesIntoViewport(data) {
|
||||
// This allows the template helpers to update reactively
|
||||
templateData.imageId = eventData.enabledElement.image.imageId;
|
||||
Session.set('CornerstoneNewImage' + viewportIndex, Random.id());
|
||||
|
||||
var stack = cornerstoneTools.getToolState(element, 'stack');
|
||||
if (stack && stack.data.length && stack.data[0].imageIds.length > 1) {
|
||||
var imageIdIndex = stack.data[0].imageIds.indexOf(templateData.imageId);
|
||||
ViewerData[contentId].viewer.imageViewerLoadedSeriesDictionary[viewportIndex].currentImageIdIndex = imageIdIndex;
|
||||
Session.set('ViewerData', ViewerData);
|
||||
}
|
||||
}
|
||||
|
||||
$(element).off('CornerstoneNewImage', onNewImage);
|
||||
$(element).on('CornerstoneNewImage', onNewImage);
|
||||
|
||||
function sendActivationTrigger(e, eventData) {
|
||||
var activeViewportIndex = Session.get('ActiveViewport');
|
||||
var activeViewportIndex = data.activeViewport.curValue;
|
||||
var viewportIndex = $(".imageViewerViewport").index(eventData.element);
|
||||
if (viewportIndex === activeViewportIndex) {
|
||||
return;
|
||||
@ -151,61 +186,62 @@ function loadSeriesIntoViewport(data) {
|
||||
|
||||
Session.set('CornerstoneNewImage' + viewportIndex, Random.id());
|
||||
|
||||
OHIF.viewer.imageViewerLoadedSeriesDictionary[viewportIndex] = {
|
||||
studyInstanceUid: data.studyInstanceUid,
|
||||
seriesInstanceUid: data.seriesInstanceUid,
|
||||
currentImageIdIndex: data.currentImageIdIndex,
|
||||
viewport: viewport
|
||||
};
|
||||
|
||||
if (OHIF.viewer.refLinesEnabled && imagePlane && imagePlane.frameOfReferenceUID) {
|
||||
OHIF.viewer.updateImageSynchronizer.add(element);
|
||||
displayReferenceLines(viewportIndex);
|
||||
}
|
||||
|
||||
}, function(error) {
|
||||
if (errorLoadingHandler) {
|
||||
errorLoadingHandler(element, imageId, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Template.imageViewerViewport.onRendered(function() {
|
||||
var studies = this.data.studies;
|
||||
var viewport = this.find(".imageViewerViewport");
|
||||
var viewportIndex = $(".imageViewerViewport").index(viewport);
|
||||
|
||||
this.data.viewportIndex = viewportIndex;
|
||||
var element = this.find(".imageViewerViewport");
|
||||
|
||||
var data = {
|
||||
viewport: viewport
|
||||
element: element,
|
||||
viewport: this.data.viewport,
|
||||
currentImageIdIndex: this.data.currentImageIdIndex,
|
||||
activeViewport: this.data.activeViewport,
|
||||
studyInstanceUid: this.data.studyInstanceUid,
|
||||
seriesInstanceUid: this.data.seriesInstanceUid
|
||||
};
|
||||
|
||||
if (this.data.seriesInstanceUid !== undefined && this.data.studyInstanceUid !== undefined) {
|
||||
var studyInstanceUid = this.data.studyInstanceUid;
|
||||
var seriesInstanceUid = this.data.seriesInstanceUid;
|
||||
|
||||
studies.every(function(study) {
|
||||
if (study.studyInstanceUid === studyInstanceUid) {
|
||||
data.study = study;
|
||||
study.seriesList.every(function(series) {
|
||||
if (series.seriesInstanceUid === seriesInstanceUid) {
|
||||
data.series = series;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
var stacks = [];
|
||||
studies.forEach(function(study) {
|
||||
study.seriesList.forEach(function(series) {
|
||||
var stack = {
|
||||
series: series,
|
||||
study: study
|
||||
};
|
||||
stacks.push(stack);
|
||||
});
|
||||
});
|
||||
|
||||
if (viewportIndex >= stacks.length) {
|
||||
viewport.classList.add('empty');
|
||||
$(viewport).siblings('.viewportInstructions').show();
|
||||
return;
|
||||
}
|
||||
data.series = stacks[viewportIndex].series;
|
||||
data.study = stacks[viewportIndex].study;
|
||||
if (this.data.seriesInstanceUid === undefined || this.data.studyInstanceUid === undefined) {
|
||||
element.classList.add('empty');
|
||||
$(element).siblings('.viewportInstructions').show();
|
||||
return;
|
||||
}
|
||||
|
||||
var studyInstanceUid = this.data.studyInstanceUid;
|
||||
var seriesInstanceUid = this.data.seriesInstanceUid;
|
||||
|
||||
studies.every(function(study) {
|
||||
if (study.studyInstanceUid === studyInstanceUid) {
|
||||
data.study = study;
|
||||
study.seriesList.every(function(series) {
|
||||
if (series.seriesInstanceUid === seriesInstanceUid) {
|
||||
data.series = series;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
loadSeriesIntoViewport(data);
|
||||
});
|
||||
|
||||
@ -216,11 +252,11 @@ Template.imageViewerViewport.onDestroyed(function() {
|
||||
|
||||
Template.imageViewerViewport.events({
|
||||
'ActivateViewport .imageViewerViewport': function(e) {
|
||||
if (this.viewportIndex === this.activeViewport.get()) {
|
||||
if (this.viewportIndex === this.activeViewport.curValue) {
|
||||
return;
|
||||
}
|
||||
console.log('ActivateViewport index: ' + this.viewportIndex);
|
||||
this.activeViewport.set(this.viewportIndex);
|
||||
this.activeViewport.curValue = this.viewportIndex;
|
||||
enablePrefetchOnElement(this.viewportIndex);
|
||||
displayReferenceLines(this.viewportIndex);
|
||||
},
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
{{ #each viewportArray }}
|
||||
<div class="viewportContainer" style="height:{{height}}%;width:{{width}}%;">
|
||||
<div class="removable">
|
||||
{{ >imageViewerViewport viewportIndex=viewportIndex studies=studies activeViewport=activeViewport}}
|
||||
{{ >imageViewerViewport viewportIndex=viewportIndex studies=studies activeViewport=activeViewport seriesInstanceUid=seriesInstanceUid studyInstanceUid=studyInstanceUid currentImageIdIndex=currentImageIdIndex viewport=viewport}}
|
||||
</div>
|
||||
</div>
|
||||
{{ /each }}
|
||||
|
||||
@ -1,50 +1,73 @@
|
||||
Template.imageViewerViewports.helpers({
|
||||
height: function() {
|
||||
var viewportRows;
|
||||
if (!Template.parentData(1).viewportRows) {
|
||||
viewportRows = 1;
|
||||
} else {
|
||||
viewportRows = Template.parentData(1).viewportRows.get();
|
||||
}
|
||||
return 100 / viewportRows;
|
||||
},
|
||||
width: function() {
|
||||
var viewportColumns;
|
||||
if (!Template.parentData(1).viewportColumns) {
|
||||
viewportColumns = 1;
|
||||
} else {
|
||||
viewportColumns = Template.parentData(1).viewportColumns.get();
|
||||
}
|
||||
return 100 / viewportColumns;
|
||||
},
|
||||
viewportArray: function() {
|
||||
// This is a really annoying thing to have to do, but Meteor
|
||||
// doesn't want to let me use another type of helper.
|
||||
var viewportRows;
|
||||
if (!this.viewportRows) {
|
||||
viewportRows = 1;
|
||||
} else {
|
||||
viewportRows = this.viewportRows.get();
|
||||
}
|
||||
height: function() {
|
||||
var viewportRows;
|
||||
if (!Template.parentData(1).viewportRows) {
|
||||
viewportRows = 1;
|
||||
} else {
|
||||
viewportRows = Template.parentData(1).viewportRows.curValue; //Having issues with .get(), not sure why?
|
||||
}
|
||||
return 100 / viewportRows;
|
||||
},
|
||||
width: function() {
|
||||
var viewportColumns;
|
||||
if (!Template.parentData(1).viewportColumns) {
|
||||
viewportColumns = 1;
|
||||
} else {
|
||||
viewportColumns = Template.parentData(1).viewportColumns.curValue; //Having issues with .get(), not sure why?
|
||||
}
|
||||
return 100 / viewportColumns;
|
||||
},
|
||||
viewportArray: function() {
|
||||
// This is a really annoying thing to have to do, but Meteor
|
||||
// doesn't want to let me use another type of helper.
|
||||
var viewportRows;
|
||||
if (!this.viewportRows) {
|
||||
viewportRows = 1;
|
||||
} else {
|
||||
viewportRows = this.viewportRows.curValue; //Having issues with .get(), not sure why?
|
||||
}
|
||||
|
||||
var viewportColumns;
|
||||
if (!this.viewportColumns) {
|
||||
viewportColumns = 1;
|
||||
} else {
|
||||
viewportColumns = this.viewportColumns.get();
|
||||
}
|
||||
|
||||
var numViewports = viewportRows * viewportColumns;
|
||||
var viewportColumns;
|
||||
if (!this.viewportColumns) {
|
||||
viewportColumns = 1;
|
||||
} else {
|
||||
viewportColumns = this.viewportColumns.curValue; //Having issues with .get(), not sure why?
|
||||
}
|
||||
|
||||
var viewportData;
|
||||
if (OHIF && OHIF.viewer && !$.isEmptyObject(OHIF.viewer.imageViewerLoadedSeriesDictionary)) {
|
||||
viewportData = OHIF.viewer.imageViewerLoadedSeriesDictionary;
|
||||
}
|
||||
|
||||
var array = [];
|
||||
for (var i=0; i < numViewports; ++i) {
|
||||
var data = {
|
||||
viewportIndex: i,
|
||||
studies: this.studies,
|
||||
activeViewport: this.activeViewport
|
||||
var hangingProtocol = getHangingProtocol();
|
||||
var inputData = {
|
||||
viewportColumns: viewportColumns,
|
||||
viewportRows: viewportRows,
|
||||
studies: this.studies
|
||||
};
|
||||
array.push(data);
|
||||
}
|
||||
return array;
|
||||
},
|
||||
var hangingProtocolViewportData = hangingProtocol(inputData);
|
||||
|
||||
var array = [];
|
||||
var numViewports = viewportRows * viewportColumns;
|
||||
for (var i=0; i < numViewports; ++i) {
|
||||
var data = {
|
||||
viewportIndex: i,
|
||||
studies: this.studies,
|
||||
activeViewport: this.activeViewport
|
||||
};
|
||||
if (viewportData && viewportData[i]) {
|
||||
data.seriesInstanceUid = viewportData[i].seriesInstanceUid;
|
||||
data.studyInstanceUid = viewportData[i].studyInstanceUid;
|
||||
data.currentImageIdIndex = viewportData[i].currentImageIdIndex;
|
||||
data.viewport = viewportData[i].viewport;
|
||||
} else if (hangingProtocolViewportData && hangingProtocolViewportData[i]) {
|
||||
data.seriesInstanceUid = hangingProtocolViewportData[i].seriesInstanceUid;
|
||||
data.studyInstanceUid = hangingProtocolViewportData[i].studyInstanceUid;
|
||||
data.currentImageIdIndex = hangingProtocolViewportData[i].currentImageIdIndex;
|
||||
data.viewport = hangingProtocolViewportData[i].viewport;
|
||||
}
|
||||
array.push(data);
|
||||
}
|
||||
return array;
|
||||
},
|
||||
});
|
||||
@ -37,14 +37,14 @@ Template.layoutChooser.events({
|
||||
|
||||
// Add 1 because the indices start from zero
|
||||
if (this.viewportRows) {
|
||||
this.viewportRows.set(rowIndex + 1);
|
||||
this.viewportRows.curValue = rowIndex + 1;
|
||||
data.viewportRows = this.viewportRows;
|
||||
} else {
|
||||
data.viewportRows = 1;
|
||||
}
|
||||
|
||||
if (this.viewportColumns) {
|
||||
this.viewportColumns.set(columnIndex + 1);
|
||||
this.viewportColumns.curValue = columnIndex + 1;
|
||||
data.viewportColumns = this.viewportColumns;
|
||||
} else {
|
||||
data.viewportColumns = 1;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
Template.playClipButton.helpers({
|
||||
'isPlaying': function() {
|
||||
Session.get('UpdateCINE');
|
||||
var viewportIndex = this.activeViewport.get();
|
||||
var viewportIndex = this.activeViewport.curValue;
|
||||
return !!OHIF.viewer.isPlaying[viewportIndex];
|
||||
}
|
||||
});
|
||||
@ -14,15 +14,13 @@ rerenderViewportWithNewSeries = function(element, data) {
|
||||
// Update the dictionary of loaded series for the specified viewport
|
||||
OHIF.viewer.imageViewerLoadedSeriesDictionary[viewportIndex] = {
|
||||
seriesInstanceUid: data.seriesInstanceUid,
|
||||
studyInstanceUid: data.studyInstanceUid
|
||||
studyInstanceUid: data.studyInstanceUid,
|
||||
currentImageIdIndex: 0
|
||||
};
|
||||
|
||||
// Remove the hover styling
|
||||
$(element).find('canvas').not('.magnifyTool').removeClass("faded");
|
||||
|
||||
// Reset the current image index for this viewport
|
||||
delete OHIF.viewer.imageViewerCurrentImageIdIndexDictionary[viewportIndex];
|
||||
|
||||
// Remove the whole template, add in the new one
|
||||
var viewportContainer = $(element).parents('.removable');
|
||||
|
||||
|
||||
@ -7,11 +7,13 @@ Template.tabTitle.events({
|
||||
var newActiveTabIndex = Math.max(tabIndex - 1, 0);
|
||||
var newActiveTab = $(".tabTitle").eq(newActiveTabIndex);
|
||||
var newActiveTabLink = newActiveTab.find("a[data-toggle=tab]");
|
||||
var newContentId = newActiveTabLink.data("target");
|
||||
var newContentId = newActiveTabLink.data("target").replace("#", "");
|
||||
|
||||
switchToTab(newContentId);
|
||||
|
||||
var tabObjectId = tabs.find({contentid: contentId}).fetch()[0]._id;
|
||||
tabs.remove(tabObjectId);
|
||||
|
||||
delete ViewerData[contentId];
|
||||
}
|
||||
});
|
||||
@ -1,5 +1,5 @@
|
||||
<template name="worklist">
|
||||
<ul class="nav" role="tablist" id="tablist">
|
||||
<ul class="nav noselect" role="tablist" id="tablist">
|
||||
<li role="presentation" class="tabTitle active">
|
||||
<a data-target="#worklistTab" role="tab" data-toggle="tab">Study List</a>
|
||||
</li>
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
ViewerData = Session.get('ViewerData') || {};
|
||||
|
||||
Template.worklist.helpers({
|
||||
'tabs': function() {
|
||||
console.log('Updating tabs');
|
||||
@ -7,29 +9,25 @@ Template.worklist.helpers({
|
||||
|
||||
Template.worklist.events({
|
||||
'click a[data-toggle="tab"]': function(e) {
|
||||
var contentId = $(e.currentTarget).data('target');
|
||||
var contentId = $(e.currentTarget).data('target').replace("#", "");
|
||||
switchToTab(contentId);
|
||||
}
|
||||
});
|
||||
|
||||
switchToTab = function(contentId) {
|
||||
var data = Session.get('DataInTab' + contentId);
|
||||
|
||||
var newContentId = contentId.replace("#", "");
|
||||
//var tabObject = tabs.findOne({contentid: newContentId});
|
||||
//tabObject.active = true;
|
||||
var data = ViewerData[contentId];
|
||||
|
||||
console.log("Switching to tab: " + contentId);
|
||||
$('.tabTitle a[data-target="' + contentId + '"]').tab('show');
|
||||
$('.tabTitle a[data-target="#' + contentId + '"]').tab('show');
|
||||
|
||||
$("#viewer").remove();
|
||||
|
||||
var container = $('.tab-content').find(contentId).get(0);
|
||||
var container = $('.tab-content').find("#" + contentId).get(0);
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (contentId === '#worklistTab') {
|
||||
if (contentId === 'worklistTab') {
|
||||
console.log('Switching to worklist');
|
||||
document.body.style.overflow = null;
|
||||
document.body.style.height = null;
|
||||
@ -50,9 +48,28 @@ switchToTab = function(contentId) {
|
||||
|
||||
Template.worklist.onRendered(function() {
|
||||
this.autorun(function() {
|
||||
var contentId = Session.get('OpenNewTabEvent');
|
||||
if (contentId) {
|
||||
switchToTab("#" + contentId);
|
||||
var data = Session.get('OpenNewTabEvent');
|
||||
|
||||
// If we have no new tab data, stop here
|
||||
// (e.g. if we are rendering the worklist)
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
var contentId = data.contentid;
|
||||
if (ViewerData.hasOwnProperty(contentId)) {
|
||||
console.warn('Contentid already exists?');
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the viewer data object
|
||||
ViewerData[contentId] = {
|
||||
contentId: contentId,
|
||||
studies: data.studies,
|
||||
title: data.title
|
||||
};
|
||||
Session.set('ViewerData', ViewerData);
|
||||
|
||||
switchToTab(contentId);
|
||||
});
|
||||
});
|
||||
@ -1,5 +1,13 @@
|
||||
body
|
||||
background-color: #202020
|
||||
|
||||
.noselect
|
||||
-webkit-touch-callout: none
|
||||
-webkit-user-select: none
|
||||
-khtml-user-select: none
|
||||
-moz-user-select: none
|
||||
-ms-user-select: none
|
||||
user-select: none
|
||||
|
||||
#worklistTab
|
||||
background-color: #202020
|
||||
|
||||
@ -38,5 +38,7 @@ Package.onUse(function (api) {
|
||||
api.export('generateUUID', 'client');
|
||||
|
||||
api.export('switchToTab', 'client');
|
||||
|
||||
api.export('ViewerData', 'client');
|
||||
});
|
||||
|
||||
|
||||
20
etc/nodeCORSProxy.example
Normal file
20
etc/nodeCORSProxy.example
Normal file
@ -0,0 +1,20 @@
|
||||
// This sets up a proxy to work around CORS.
|
||||
// See http://chafey.blogspot.be/2014/09/working-around-cors.html
|
||||
// For more information
|
||||
var http = require('http'),
|
||||
httpProxy = require('http-proxy');
|
||||
|
||||
var proxy = httpProxy.createProxyServer({
|
||||
target: 'http://localhost:8042',
|
||||
auth: 'orthanc:orthanc'
|
||||
}).listen(8042);
|
||||
|
||||
proxy.on('proxyRes', function(proxyReq, req, res, options) {
|
||||
// add the CORS header to the response
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
});
|
||||
|
||||
proxy.on('error', function(e) {
|
||||
// suppress errors
|
||||
console.log(e);
|
||||
});
|
||||
20
etc/nodeCORSProxy.js
Normal file
20
etc/nodeCORSProxy.js
Normal file
@ -0,0 +1,20 @@
|
||||
// This sets up a proxy to work around CORS.
|
||||
// See http://chafey.blogspot.be/2014/09/working-around-cors.html
|
||||
// For more information
|
||||
var http = require('http'),
|
||||
httpProxy = require('http-proxy');
|
||||
|
||||
var proxy = httpProxy.createProxyServer({
|
||||
target: 'http://192.168.99.100:8042',
|
||||
auth: 'orthanc:orthanc'
|
||||
}).listen(8042);
|
||||
|
||||
proxy.on('proxyRes', function(proxyReq, req, res, options) {
|
||||
// add the CORS header to the response
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
});
|
||||
|
||||
proxy.on('error', function(e) {
|
||||
// suppress errors
|
||||
console.log(e);
|
||||
});
|
||||
11
etc/setupLocalOrthanc.sh
Executable file
11
etc/setupLocalOrthanc.sh
Executable file
@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
echo "Retrieving docker default machine IP address..."
|
||||
MACHINEIP=$(docker-machine ip default)
|
||||
echo "Copying nodeCORSProxy.example to nodeCORSProxy.js"
|
||||
cp nodeCORSProxy.example nodeCORSProxy.js
|
||||
sed -i '' "s/localhost:8042/$MACHINEIP:8042/g" nodeCORSProxy.js
|
||||
echo "Installing node http-proxy from npm..."
|
||||
npm install http-proxy
|
||||
echo "Running proxy server..."
|
||||
echo "In a separate tab/terminal, go to /main and run ./bin/localhostOrthanc.sh to start the Meteor server"
|
||||
node nodeCORSProxy.js
|
||||
Loading…
Reference in New Issue
Block a user