Updates to save viewport data across sessions / while switching tabs

This commit is contained in:
Erik Ziegler 2015-10-23 14:08:51 +02:00
parent 56417df385
commit 4721400b34
27 changed files with 585 additions and 204 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
.meteor/local
.meteor/meteorite
node_modules

View File

@ -0,0 +1,5 @@
body
background-color: #202020
#search-panel-head
background-color: #8845AB

View File

@ -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

View File

@ -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

View File

@ -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);
});
}
});

View File

@ -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

View File

@ -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);
});
}
});

View 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);

View 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);

View 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);

View File

@ -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');
});

View File

@ -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">

View File

@ -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

View File

@ -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);
},

View File

@ -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 }}

View File

@ -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;
},
});

View File

@ -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;

View File

@ -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];
}
});

View File

@ -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');

View File

@ -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];
}
});

View File

@ -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>

View File

@ -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);
});
});

View File

@ -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

View File

@ -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
View 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
View 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
View 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