Updates to improve reactivity, session storage, logging

This commit is contained in:
Erik Ziegler 2015-10-30 12:21:59 +01:00
parent 9fb5cc2953
commit a10f149b4b
37 changed files with 370 additions and 288 deletions

View File

@ -36,3 +36,4 @@ random
reactive-var reactive-var
reactive-dict reactive-dict
lesiontracker lesiontracker
practicalmeteor:loglevel

View File

@ -16,6 +16,7 @@ caching-compiler@1.0.0
caching-html-compiler@1.0.2 caching-html-compiler@1.0.2
callback-hook@1.0.4 callback-hook@1.0.4
check@1.1.0 check@1.1.0
coffeescript@1.0.11
cornerstone@0.0.1 cornerstone@0.0.1
ddp@1.2.2 ddp@1.2.2
ddp-client@1.2.1 ddp-client@1.2.1
@ -67,6 +68,8 @@ npm-bcrypt@0.7.8_2
npm-mongo@1.4.39_1 npm-mongo@1.4.39_1
observe-sequence@1.0.7 observe-sequence@1.0.7
ordered-dict@1.0.4 ordered-dict@1.0.4
practicalmeteor:chai@1.9.2_3
practicalmeteor:loglevel@1.1.0_3
promise@0.5.1 promise@0.5.1
random@1.0.5 random@1.0.5
rate-limit@1.0.0 rate-limit@1.0.0

View File

@ -1,10 +1,10 @@
<template name="viewer"> <template name="viewer">
<div id="viewer" class="viewer"> <div id="viewer" class="viewer">
{{ >lesionDialog studies=studies activeViewport=activeViewport}} {{ >lesionDialog }}
<div id="upperHalf"> <div id="upperHalf">
{{ >hidingPanel studies=studies activeViewport=activeViewport}} {{ >hidingPanel }}
{{ >viewerMain studies=studies activeViewport=activeViewport viewportRows=viewportRows viewportColumns=viewportColumns}} {{ >viewerMain }}
</div> </div>
{{ >lesionTable studies=studies activeViewport=activeViewport viewportRows=viewportRows viewportColumns=viewportColumns}} {{ >lesionTable }}
</div> </div>
</template> </template>

View File

@ -1,4 +1,6 @@
function resizeViewports() { function resizeViewports() {
log.info("viewer resizeViewports");
// Handle resizing of image viewer viewports // Handle resizing of image viewer viewports
// For some reason, this seems to need to be on // For some reason, this seems to need to be on
// another delay, or the resizing won't work properly // another delay, or the resizing won't work properly
@ -15,7 +17,8 @@ function resizeViewports() {
} }
Template.viewer.onCreated(function() { Template.viewer.onCreated(function() {
console.log("Image Viewer onCreated"); log.info("viewer onCreated");
OHIF = { OHIF = {
viewer: {} viewer: {}
}; };
@ -55,35 +58,35 @@ Template.viewer.onCreated(function() {
}; };
} }
if (this.data.activeViewport === undefined) {
this.data.activeViewport = new ReactiveVar(0);
}
if (this.data.viewportRows === undefined) {
this.data.viewportRows = new ReactiveVar(1);
}
if (this.data.viewportColumns === undefined) {
this.data.viewportColumns = new ReactiveVar(2);
}
var contentId = this.data.contentId; var contentId = this.data.contentId;
// Update the viewer data object if (ViewerData[contentId].loadedSeriesData) {
ViewerData[contentId].viewportColumns = this.data.viewportColumns; log.info('Reloading previous loadedSeriesData');
ViewerData[contentId].viewportRows = this.data.viewportRows;
ViewerData[contentId].activeViewport = this.data.activeViewport; OHIF.viewer.loadedSeriesData = ViewerData[contentId].loadedSeriesData;
Session.set('ViewerData', ViewerData);
if (ViewerData[contentId].viewer) {
OHIF.viewer = ViewerData[contentId].viewer;
} else { } else {
OHIF.viewer.imageViewerLoadedSeriesDictionary = {}; log.info('Setting default ViewerData');
ViewerData[contentId].viewer = OHIF.viewer; OHIF.viewer.loadedSeriesData = {};
ViewerData[contentId].loadedSeriesData = OHIF.viewer.loadedSeriesData;
// Update the viewer data object
ViewerData[contentId].viewportColumns = 2;
ViewerData[contentId].viewportRows = 1;
ViewerData[contentId].activeViewport = 0;
Session.set('ViewerData', ViewerData);
} }
Session.set('activeViewport', ViewerData[contentId].activeViewport || 0);
Session.set("studies", this.data.studies);
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");
OHIF.viewer.updateImageSynchronizer.destroy(); OHIF.viewer.updateImageSynchronizer.destroy();
}); });

View File

@ -1,6 +1,6 @@
<template name="viewerMain"> <template name="viewerMain">
<div class="viewerMain"> <div class="viewerMain">
{{ >toolbar toolbarOptions=toolbarOptions activeViewport=activeViewport viewportRows=viewportRows viewportColumns=viewportColumns}} {{ >toolbar toolbarOptions=toolbarOptions }}
{{ >imageViewerViewports studies=studies activeViewport=activeViewport viewportRows=viewportRows viewportColumns=viewportColumns}} {{ >imageViewerViewports }}
</div> </div>
</template> </template>

View File

@ -1,7 +1,4 @@
Template.viewerMain.helpers({ Template.viewerMain.helpers({
studies : function() {
return this.studies;
},
'toolbarOptions': function() { 'toolbarOptions': function() {
var toolbarOptions = {}; var toolbarOptions = {};

View File

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

View File

@ -1,49 +0,0 @@
Router.configure({
layoutTemplate: 'layoutLesionTracker',
//loadingTemplate: '',
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',
name: 'viewer',
onBeforeAction: function() {
var self = this;
Meteor.call('GetStudyMetadata', this.params._id, function(error, study) {
sortStudy(study);
var title = study.seriesList[0].instances[0].patientName;
var contentid = generateUUID();
var data = {
title: title,
contentid: contentid,
};
tabs.insert(data);
data.studies = [study];
self.render('worklist');
Session.set('OpenNewTabEvent', data);
});
}
});

View File

@ -0,0 +1,45 @@
tabs = new Meteor.Collection(null);
Session.setDefault('ViewerData', {});
// Re-add any tab data saved in the Session
Object.keys(ViewerData).forEach(function(contentId) {
var tabData = ViewerData[contentId];
var data = {
title: tabData.title,
contentid: tabData.contentid,
};
tabs.insert(data);
});
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'layout',
notFoundTemplate: 'notFound'
});
Router.onBeforeAction('loading');
Router.route('/', function () {
this.render('worklist');
});
Router.route('/viewer/:_id', {
layoutTemplate: 'layout',
name: 'viewer',
onBeforeAction: function() {
log.info('Router GetStudyMetadata');
var studyInstanceUid = this.params._id;
// Check if this study is already loaded in a tab
// If it is, stop here so we don't keep adding tabs on hot-code reloads
var tab = tabs.find({'studyInstanceUid' : studyInstanceUid}).fetch();
if (tab) {
return;
}
this.render('worklist');
openNewTab(studyInstanceUid);
}
});

View File

@ -33,3 +33,4 @@ dicomweb
viewerbase viewerbase
worklist worklist
reactive-var reactive-var
practicalmeteor:loglevel

View File

@ -15,6 +15,7 @@ caching-compiler@1.0.0
caching-html-compiler@1.0.2 caching-html-compiler@1.0.2
callback-hook@1.0.4 callback-hook@1.0.4
check@1.1.0 check@1.1.0
coffeescript@1.0.11
cornerstone@0.0.1 cornerstone@0.0.1
ddp@1.2.2 ddp@1.2.2
ddp-client@1.2.1 ddp-client@1.2.1
@ -64,6 +65,8 @@ npm-bcrypt@0.7.8_2
npm-mongo@1.4.39_1 npm-mongo@1.4.39_1
observe-sequence@1.0.7 observe-sequence@1.0.7
ordered-dict@1.0.4 ordered-dict@1.0.4
practicalmeteor:chai@1.9.2_3
practicalmeteor:loglevel@1.1.0_3
promise@0.5.1 promise@0.5.1
random@1.0.5 random@1.0.5
rate-limit@1.0.0 rate-limit@1.0.0

View File

@ -1,6 +1,9 @@
<template name="viewer"> <template name="viewer">
<div id="viewer"> <div id="viewer">
{{> studyBrowser studies=studies activeViewport=activeViewport}} {{> studyBrowser }}
{{> viewerMain studies=studies activeViewport=activeViewport viewportRows=viewportRows viewportColumns=viewportColumns}} <div class='viewerMain'>
{{ > toolbar }}
{{ > imageViewerViewports }}
</div>
</div> </div>
</template> </template>

View File

@ -1,4 +1,6 @@
function resizeViewports() { function resizeViewports() {
log.info("viewer resizeViewports");
// Handle resizing of image viewer viewports // Handle resizing of image viewer viewports
// For some reason, this seems to need to be on // For some reason, this seems to need to be on
// another delay, or the resizing won't work properly // another delay, or the resizing won't work properly
@ -15,7 +17,8 @@ function resizeViewports() {
} }
Template.viewer.onCreated(function() { Template.viewer.onCreated(function() {
console.log("Image Viewer onCreated"); log.info("viewer onCreated");
OHIF = { OHIF = {
viewer: {} viewer: {}
}; };
@ -55,35 +58,35 @@ Template.viewer.onCreated(function() {
}; };
} }
if (this.data.activeViewport === undefined) {
this.data.activeViewport = new ReactiveVar(0);
}
if (this.data.viewportRows === undefined) {
this.data.viewportRows = new ReactiveVar(1);
}
if (this.data.viewportColumns === undefined) {
this.data.viewportColumns = new ReactiveVar(1);
}
var contentId = this.data.contentId; var contentId = this.data.contentId;
// Update the viewer data object if (ViewerData[contentId].loadedSeriesData) {
ViewerData[contentId].viewportColumns = this.data.viewportColumns; log.info('Reloading previous loadedSeriesData');
ViewerData[contentId].viewportRows = this.data.viewportRows;
ViewerData[contentId].activeViewport = this.data.activeViewport; OHIF.viewer.loadedSeriesData = ViewerData[contentId].loadedSeriesData;
Session.set('ViewerData', ViewerData);
if (ViewerData[contentId].viewer) {
OHIF.viewer = ViewerData[contentId].viewer;
} else { } else {
OHIF.viewer.imageViewerLoadedSeriesDictionary = {}; log.info('Setting default ViewerData');
ViewerData[contentId].viewer = OHIF.viewer; OHIF.viewer.loadedSeriesData = {};
ViewerData[contentId].loadedSeriesData = OHIF.viewer.loadedSeriesData;
// Update the viewer data object
ViewerData[contentId].viewportColumns = 1;
ViewerData[contentId].viewportRows = 1;
ViewerData[contentId].activeViewport = 0;
Session.set('ViewerData', ViewerData);
} }
Session.set('activeViewport', ViewerData[contentId].activeViewport || 0);
Session.set("studies", this.data.studies);
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");
OHIF.viewer.updateImageSynchronizer.destroy(); OHIF.viewer.updateImageSynchronizer.destroy();
}); });

View File

@ -13,3 +13,9 @@
.studyBrowser .studyBrowser
width: auto width: auto
.viewerMain
float: left
background-color: gray
height: 100%
width: calc(100% - 120px)

View File

@ -1,6 +0,0 @@
<template name="viewerMain">
<div class='viewerMain'>
{{ > toolbar activeViewport=activeViewport viewportRows=viewportRows viewportColumns=viewportColumns}}
{{ > imageViewerViewports studies=studies activeViewport=activeViewport viewportRows=viewportRows viewportColumns=viewportColumns}}
</div>
</template>

View File

@ -1,5 +0,0 @@
Template.viewerMain.helpers({
studies : function() {
return this.studies;
}
});

View File

@ -1,5 +0,0 @@
.viewerMain
float: left
background-color: gray
height: 100%
width: calc(100% - 120px)

3
OHIFViewer/client/log.js Normal file
View File

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

View File

@ -1,49 +1,45 @@
Router.configure({
layoutTemplate: 'layout',
//loadingTemplate: '',
notFoundTemplate: 'notFound'
});
Router.route('/', function () {
this.render('worklist', {});
});
tabs = new Meteor.Collection(null); tabs = new Meteor.Collection(null);
Session.setDefault('ViewerData', {}); Session.setDefault('ViewerData', {});
Session.setDefault('ViewerDataUpdated', Random.id());
// Re-add any tab data saved in the Session
Object.keys(ViewerData).forEach(function(contentId) { Object.keys(ViewerData).forEach(function(contentId) {
var tabData = ViewerData[contentId]; var tabData = ViewerData[contentId];
var data = { var data = {
title: tabData.title, title: tabData.title,
contentid: tabData.contentId, contentid: tabData.contentid,
}; };
tabs.insert(data); tabs.insert(data);
}); });
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'layout',
notFoundTemplate: 'notFound'
});
Router.onBeforeAction('loading');
Router.route('/', function () {
this.render('worklist');
});
Router.route('/viewer/:_id', { Router.route('/viewer/:_id', {
layoutTemplate: 'layout', layoutTemplate: 'layout',
name: 'viewer', name: 'viewer',
onBeforeAction: function() { onBeforeAction: function() {
var self = this; log.info('Router GetStudyMetadata');
Meteor.call('GetStudyMetadata', this.params._id, function(error, study) { var studyInstanceUid = this.params._id;
sortStudy(study);
var title = study.seriesList[0].instances[0].patientName; // Check if this study is already loaded in a tab
var contentid = generateUUID(); // If it is, stop here so we don't keep adding tabs on hot-code reloads
var tab = tabs.find({'studyInstanceUid' : studyInstanceUid}).fetch();
if (tab) {
return;
}
var data = { this.render('worklist');
title: title, openNewTab(studyInstanceUid);
contentid: contentid,
};
tabs.insert(data);
data.studies = [study];
self.render('worklist');
Session.set('OpenNewTabEvent', data);
});
} }
}); });

View File

@ -2148,7 +2148,7 @@ if(typeof cornerstone === 'undefined'){
function initShaders() { function initShaders() {
for (var id in cornerstone.webGL.shaders) { for (var id in cornerstone.webGL.shaders) {
console.log("WEBGL: Loading shader", id); //console.log("WEBGL: Loading shader", id);
var shader = cornerstone.webGL.shaders[ id ]; var shader = cornerstone.webGL.shaders[ id ];
shader.attributes = {}; shader.attributes = {};
shader.uniforms = {}; shader.uniforms = {};
@ -2168,13 +2168,13 @@ if(typeof cornerstone === 'undefined'){
function initRenderer() { function initRenderer() {
if (cornerstone.webGL.isWebGLInitialized === true) { if (cornerstone.webGL.isWebGLInitialized === true) {
console.log("WEBGL Renderer already initialized"); //console.log("WEBGL Renderer already initialized");
return; return;
} }
if ( initWebGL( renderCanvas ) ) { if ( initWebGL( renderCanvas ) ) {
initBuffers(); initBuffers();
initShaders(); initShaders();
console.log("WEBGL Renderer initialized!"); //console.log("WEBGL Renderer initialized!");
cornerstone.webGL.isWebGLInitialized = true; cornerstone.webGL.isWebGLInitialized = true;
} }
} }

View File

@ -3,12 +3,22 @@ TabsTimepoints = new Meteor.Collection(null);
Template.lesionTable.helpers({ Template.lesionTable.helpers({
'measurement': function() { 'measurement': function() {
<<<<<<< HEAD
var contentId = Session.get("activeContentId"); var contentId = Session.get("activeContentId");
return Measurements.find({contentId: contentId}); return Measurements.find({contentId: contentId});
}, },
'tabTimepoints': function() { 'tabTimepoints': function() {
var contentId = Session.get("activeContentId"); var contentId = Session.get("activeContentId");
return TabsTimepoints.find({contentId: contentId}); return TabsTimepoints.find({contentId: contentId});
=======
var contentId = this.contentId;
console.log(Measurements.find({contentId: contentId}));
return Measurements.find({contentId: contentId});
},
'timepointNames': function() {
var contentId = this.contentId;
return Template.instance().timepointNamesDictionary.get(contentId);
>>>>>>> Updates to improve reactivity, session storage, logging
}, },
'lesionData': function() { 'lesionData': function() {
var array = []; var array = [];
@ -21,19 +31,16 @@ Template.lesionTable.helpers({
}); });
Template.lesionTable.onRendered(function() { Template.lesionTable.onRendered(function() {
var contentId = this.data.contentId;
var viewportColumns = ViewerData[contentId].viewportColumns;
var viewportRows = ViewerData[contentId].viewportRows;
var cols = Template.instance().data.viewportColumns.curValue; var totalViewports = viewportColumns * viewportRows;
var rows = Template.instance().data.viewportRows.curValue;
var totalViewports = cols * rows;
var contentId = Session.get('activeContentId');
var timepointsArray = [];
for(var i=0; i< totalViewports; i++) { for(var i=0; i< totalViewports; i++) {
var timepointID = contentId.toString() + i.toString();
var timepointID = contentId.toString()+ i.toString();
var timepointName = "Baseline"; var timepointName = "Baseline";
if(i > 0) { if (i > 0) {
timepointName = "Follow Up "+i; timepointName = "Follow Up "+i;
} }
var timepointObject = {timepointID: timepointID, timepointName: timepointName}; var timepointObject = {timepointID: timepointID, timepointName: timepointName};
@ -43,7 +50,7 @@ Template.lesionTable.onRendered(function() {
// Prevent duplicate data when onRendered is called // Prevent duplicate data when onRendered is called
var tabTimepoint = TabsTimepoints.find({contentId: contentId}).fetch(); var tabTimepoint = TabsTimepoints.find({contentId: contentId}).fetch();
if (tabTimepoint != undefined && tabTimepoint.length > 0) { if (tabTimepoint !== undefined && tabTimepoint.length > 0) {
// Update timepoints // Update timepoints
TabsTimepoints.update( TabsTimepoints.update(
{ contentId: contentId}, { contentId: contentId},

View File

@ -1,5 +1,5 @@
Template.studyBrowser.helpers({ Template.studyBrowser.helpers({
studies : function() { studies : function() {
return this.studies; return Session.get('studies');
} }
}); });

View File

@ -6,8 +6,9 @@
* @param element * @param element
*/ */
function enablePrefetchOnElement(viewportIndex) { function enablePrefetchOnElement(viewportIndex) {
log.info("imageViewerViewport enablePrefetchOnElement");
var element = $('.imageViewerViewport').get(viewportIndex); var element = $('.imageViewerViewport').get(viewportIndex);
//console.log('Enabling prefetch on new element');
// Loop through all viewports and disable stackPrefetch // Loop through all viewports and disable stackPrefetch
$('.imageViewerViewport').each(function() { $('.imageViewerViewport').each(function() {
@ -25,6 +26,8 @@ function enablePrefetchOnElement(viewportIndex) {
} }
function displayReferenceLines(viewportIndex) { function displayReferenceLines(viewportIndex) {
log.info("imageViewerViewport displayReferenceLines");
var element = $('.imageViewerViewport').get(viewportIndex); var element = $('.imageViewerViewport').get(viewportIndex);
$('.imageViewerViewport').each(function(index, element) { $('.imageViewerViewport').each(function(index, element) {
@ -49,17 +52,22 @@ function displayReferenceLines(viewportIndex) {
} }
function loadSeriesIntoViewport(data) { function loadSeriesIntoViewport(data) {
log.info("imageViewerViewport loadSeriesIntoViewport");
if (!data.series || !data.element) { if (!data.series || !data.element) {
return; return;
} }
var contentId = $("#viewer").parents('.tab-pane.active').attr('id'); var contentId = Session.get('activeContentId');
var study = data.study; var study = data.study;
var series = data.series; var series = data.series;
var element = data.element; var element = data.element;
var viewportIndex = $(".imageViewerViewport").index(element); var viewportIndex = $(".imageViewerViewport").index(element);
ViewerData[contentId].loadedSeriesData[viewportIndex] = {};
var allEvents = 'CornerstoneToolsMouseDown CornerstoneToolsMouseDownActivate ' + var allEvents = 'CornerstoneToolsMouseDown CornerstoneToolsMouseDownActivate ' +
'CornerstoneToolsMouseClick CornerstoneToolsMouseDrag CornerstoneToolsMouseUp ' + 'CornerstoneToolsMouseClick CornerstoneToolsMouseDrag CornerstoneToolsMouseUp ' +
'CornerstoneToolsMouseWheel CornerstoneToolsTap CornerstoneToolsTouchPress ' + 'CornerstoneToolsMouseWheel CornerstoneToolsTap CornerstoneToolsTouchPress ' +
@ -115,6 +123,8 @@ function loadSeriesIntoViewport(data) {
endLoadingHandler(element); endLoadingHandler(element);
} }
cornerstone.resize(element, true);
element.classList.remove('empty'); element.classList.remove('empty');
$(element).siblings('.viewportInstructions').hide(); $(element).siblings('.viewportInstructions').hide();
$(element).siblings('.imageViewerViewportOverlay').show(); $(element).siblings('.imageViewerViewportOverlay').show();
@ -142,9 +152,11 @@ function loadSeriesIntoViewport(data) {
cornerstoneTools.magnify.enable(element); cornerstoneTools.magnify.enable(element);
function onImageRendered(e, eventData) { function onImageRendered(e, eventData) {
log.info('imageViewerViewport onImageRendered');
Session.set('CornerstoneImageRendered' + viewportIndex, Random.id()); Session.set('CornerstoneImageRendered' + viewportIndex, Random.id());
var viewport = cornerstone.getViewport(element); var viewport = cornerstone.getViewport(element);
ViewerData[contentId].viewer.imageViewerLoadedSeriesDictionary[viewportIndex].viewport = viewport; ViewerData[contentId].loadedSeriesData[viewportIndex].viewport = viewport;
Session.set('ViewerData', ViewerData); Session.set('ViewerData', ViewerData);
} }
@ -153,6 +165,8 @@ function loadSeriesIntoViewport(data) {
Session.set('CornerstoneImageRendered' + viewportIndex, Random.id()); Session.set('CornerstoneImageRendered' + viewportIndex, Random.id());
function onNewImage(e, eventData) { function onNewImage(e, eventData) {
log.info('imageViewerViewport onNewImage');
// Update the templateData with the new imageId // Update the templateData with the new imageId
// This allows the template helpers to update reactively // This allows the template helpers to update reactively
templateData.imageId = eventData.enabledElement.image.imageId; templateData.imageId = eventData.enabledElement.image.imageId;
@ -161,7 +175,7 @@ function loadSeriesIntoViewport(data) {
var stack = cornerstoneTools.getToolState(element, 'stack'); var stack = cornerstoneTools.getToolState(element, 'stack');
if (stack && stack.data.length && stack.data[0].imageIds.length > 1) { if (stack && stack.data.length && stack.data[0].imageIds.length > 1) {
var imageIdIndex = stack.data[0].imageIds.indexOf(templateData.imageId); var imageIdIndex = stack.data[0].imageIds.indexOf(templateData.imageId);
ViewerData[contentId].viewer.imageViewerLoadedSeriesDictionary[viewportIndex].currentImageIdIndex = imageIdIndex; ViewerData[contentId].loadedSeriesData[viewportIndex].currentImageIdIndex = imageIdIndex;
Session.set('ViewerData', ViewerData); Session.set('ViewerData', ViewerData);
} }
} }
@ -170,14 +184,17 @@ function loadSeriesIntoViewport(data) {
$(element).on('CornerstoneNewImage', onNewImage); $(element).on('CornerstoneNewImage', onNewImage);
function sendActivationTrigger(e, eventData) { function sendActivationTrigger(e, eventData) {
var activeViewportIndex = data.activeViewport.curValue; log.info('imageViewerViewport sendActivationTrigger');
var activeViewportIndex = Session.get('activeViewport');
var viewportIndex = $(".imageViewerViewport").index(eventData.element); var viewportIndex = $(".imageViewerViewport").index(eventData.element);
if (viewportIndex === activeViewportIndex) { if (viewportIndex === activeViewportIndex) {
return; return;
} }
eventData.viewportIndex = viewportIndex; eventData.viewportIndex = viewportIndex;
var customEvent = jQuery.Event('ActivateViewport', eventData); var customEvent = $.Event('ActivateViewport', eventData);
customEvent.type = 'ActivateViewport'; // Need to overwrite the type set in the touch tools
// Need to overwrite the type set in the original event
customEvent.type = 'ActivateViewport';
$(e.target).trigger(customEvent, eventData); $(e.target).trigger(customEvent, eventData);
} }
@ -186,7 +203,7 @@ function loadSeriesIntoViewport(data) {
Session.set('CornerstoneNewImage' + viewportIndex, Random.id()); Session.set('CornerstoneNewImage' + viewportIndex, Random.id());
OHIF.viewer.imageViewerLoadedSeriesDictionary[viewportIndex] = { OHIF.viewer.loadedSeriesData[viewportIndex] = {
studyInstanceUid: data.studyInstanceUid, studyInstanceUid: data.studyInstanceUid,
seriesInstanceUid: data.seriesInstanceUid, seriesInstanceUid: data.seriesInstanceUid,
currentImageIdIndex: data.currentImageIdIndex, currentImageIdIndex: data.currentImageIdIndex,
@ -206,16 +223,19 @@ function loadSeriesIntoViewport(data) {
} }
Template.imageViewerViewport.onRendered(function() { Template.imageViewerViewport.onRendered(function() {
var studies = this.data.studies; log.info("imageViewerViewport onRendered");
var studies = Session.get('studies');
var activeViewport = Session.get('activeViewport');
var element = this.find(".imageViewerViewport"); var element = this.find(".imageViewerViewport");
var data = { var data = {
element: element, element: element,
viewport: this.data.viewport, viewport: this.data.viewport,
currentImageIdIndex: this.data.currentImageIdIndex, currentImageIdIndex: this.data.currentImageIdIndex,
activeViewport: this.data.activeViewport,
studyInstanceUid: this.data.studyInstanceUid, studyInstanceUid: this.data.studyInstanceUid,
seriesInstanceUid: this.data.seriesInstanceUid seriesInstanceUid: this.data.seriesInstanceUid,
activeViewport: activeViewport
}; };
if (this.data.seriesInstanceUid === undefined || this.data.studyInstanceUid === undefined) { if (this.data.seriesInstanceUid === undefined || this.data.studyInstanceUid === undefined) {
@ -246,18 +266,23 @@ Template.imageViewerViewport.onRendered(function() {
}); });
Template.imageViewerViewport.onDestroyed(function() { Template.imageViewerViewport.onDestroyed(function() {
log.info("imageViewerViewport onDestroyed");
var element = this.find(".imageViewerViewport"); var element = this.find(".imageViewerViewport");
try {
cornerstoneTools.stopClip(element);
} catch(error) {
}
cornerstone.disable(element); cornerstone.disable(element);
}); });
Template.imageViewerViewport.events({ Template.imageViewerViewport.events({
'ActivateViewport .imageViewerViewport': function(e) { 'ActivateViewport .imageViewerViewport': function(e) {
if (this.viewportIndex === this.activeViewport.curValue) { log.info("imageViewerViewport ActivateViewport");
return;
} Session.set("activeViewport", this.viewportIndex);
Session.set("ActivateViewportIndex", this.viewportIndex);
console.log('ActivateViewport index: ' + this.viewportIndex);
this.activeViewport.curValue = this.viewportIndex;
enablePrefetchOnElement(this.viewportIndex); enablePrefetchOnElement(this.viewportIndex);
displayReferenceLines(this.viewportIndex); displayReferenceLines(this.viewportIndex);
}, },

View File

@ -3,7 +3,7 @@
{{ #each viewportArray }} {{ #each viewportArray }}
<div class="viewportContainer" style="height:{{height}}%;width:{{width}}%;"> <div class="viewportContainer" style="height:{{height}}%;width:{{width}}%;">
<div class="removable"> <div class="removable">
{{ >imageViewerViewport viewportIndex=viewportIndex studies=studies activeViewport=activeViewport seriesInstanceUid=seriesInstanceUid studyInstanceUid=studyInstanceUid currentImageIdIndex=currentImageIdIndex viewport=viewport}} {{ >imageViewerViewport viewportIndex=viewportIndex seriesInstanceUid=seriesInstanceUid studyInstanceUid=studyInstanceUid currentImageIdIndex=currentImageIdIndex viewport=viewport}}
</div> </div>
</div> </div>
{{ /each }} {{ /each }}

View File

@ -1,49 +1,44 @@
Template.imageViewerViewports.helpers({ Template.imageViewerViewports.helpers({
height: function() { height: function() {
var viewportRows; var viewportRows = this.viewportRows || 1;
if (!Template.parentData(1).viewportRows) {
viewportRows = 1;
} else {
viewportRows = Template.parentData(1).viewportRows.curValue; //Having issues with .get(), not sure why?
}
return 100 / viewportRows; return 100 / viewportRows;
}, },
width: function() { width: function() {
var viewportColumns; var viewportColumns = this.viewportColumns || 1;
if (!Template.parentData(1).viewportColumns) {
viewportColumns = 1;
} else {
viewportColumns = Template.parentData(1).viewportColumns.curValue; //Having issues with .get(), not sure why?
}
return 100 / viewportColumns; return 100 / viewportColumns;
}, },
viewportArray: function() { viewportArray: function() {
// This is a really annoying thing to have to do, but Meteor log.info("imageViewerViewports viewportArray");
// doesn't want to let me use another type of helper.
var viewportRows; var studies = Session.get('studies');
if (!this.viewportRows) {
viewportRows = 1; var viewportRows = this.viewportRows || 1;
} else { var viewportColumns = this.viewportColumns || 1;
viewportRows = this.viewportRows.curValue; //Having issues with .get(), not sure why?
var contentId = this.contentId || $("#viewer").parents(".tab-pane.active").attr('id');
if (this.viewportRows && this.viewportColumns) {
viewportRows = this.viewportRows || 1;
viewportColumns = this.viewportColumns || 1;
} else if (ViewerData[contentId].viewportRows && ViewerData[contentId].viewportColumns) {
viewportRows = ViewerData[contentId].viewportRows;
viewportColumns = ViewerData[contentId].viewportColumns;
} }
var viewportColumns; // Update viewerData
if (!this.viewportColumns) { ViewerData[contentId].viewportRows = viewportRows;
viewportColumns = 1; ViewerData[contentId].viewportColumns = viewportColumns;
} else { Session.set("ViewerData", ViewerData);
viewportColumns = this.viewportColumns.curValue; //Having issues with .get(), not sure why?
}
var viewportData; var viewportData;
if (OHIF && OHIF.viewer && !$.isEmptyObject(OHIF.viewer.imageViewerLoadedSeriesDictionary)) { if (!$.isEmptyObject(ViewerData[contentId].loadedSeriesData)) {
viewportData = OHIF.viewer.imageViewerLoadedSeriesDictionary; viewportData = ViewerData[contentId].loadedSeriesData;
} }
var hangingProtocol = getHangingProtocol(); var hangingProtocol = getHangingProtocol();
var inputData = { var inputData = {
viewportColumns: viewportColumns, viewportColumns: viewportColumns,
viewportRows: viewportRows, viewportRows: viewportRows,
studies: this.studies studies: studies
}; };
var hangingProtocolViewportData = hangingProtocol(inputData); var hangingProtocolViewportData = hangingProtocol(inputData);
@ -52,8 +47,10 @@ Template.imageViewerViewports.helpers({
for (var i=0; i < numViewports; ++i) { for (var i=0; i < numViewports; ++i) {
var data = { var data = {
viewportIndex: i, viewportIndex: i,
studies: this.studies, // These two are necessary because otherwise the width and height helpers
activeViewport: this.activeViewport // don't get the right data context. Seems to be related to the "each" loop.
viewportColumns: viewportColumns,
viewportRows: viewportRows
}; };
if (viewportData && viewportData[i]) { if (viewportData && viewportData[i]) {
data.seriesInstanceUid = viewportData[i].seriesInstanceUid; data.seriesInstanceUid = viewportData[i].seriesInstanceUid;

View File

@ -27,31 +27,18 @@ Template.layoutChooser.events({
}, },
'click .layoutChooser table td': function(evt) { 'click .layoutChooser table td': function(evt) {
$('#imageViewerViewports').remove(); $('#imageViewerViewports').remove();
var container = $(".viewerMain").get(0);
var currentCell = $(evt.currentTarget); var currentCell = $(evt.currentTarget);
var rowIndex = currentCell.closest('tr').index(); var rowIndex = currentCell.closest('tr').index();
var columnIndex = currentCell.index(); var columnIndex = currentCell.index();
var data = {};
// Add 1 because the indices start from zero // Add 1 because the indices start from zero
if (this.viewportRows) { var data = {
this.viewportRows.curValue = rowIndex + 1; viewportRows: rowIndex + 1,
data.viewportRows = this.viewportRows; viewportColumns: columnIndex + 1,
} else { };
data.viewportRows = 1;
}
if (this.viewportColumns) { var container = $(".viewerMain").get(0);
this.viewportColumns.curValue = columnIndex + 1;
data.viewportColumns = this.viewportColumns;
} else {
data.viewportColumns = 1;
}
data.studies = Template.parentData(2).studies;
data.activeViewport = Template.parentData(2).activeViewport;
UI.renderWithData(Template.imageViewerViewports, data, container); UI.renderWithData(Template.imageViewerViewports, data, container);
} }
}); });

View File

@ -1,7 +1,7 @@
Template.playClipButton.helpers({ Template.playClipButton.helpers({
'isPlaying': function() { 'isPlaying': function() {
Session.get('UpdateCINE'); Session.get('UpdateCINE');
var viewportIndex = this.activeViewport.curValue; var activeViewport = Session.get('activeViewport');
return !!OHIF.viewer.isPlaying[viewportIndex]; return !!OHIF.viewer.isPlaying[activeViewport];
} }
}); });

View File

@ -6,11 +6,11 @@
{{ /each }} {{ /each }}
{{ #if includePlayClipButton }} {{ #if includePlayClipButton }}
{{ >playClipButton activeViewport=activeViewport}} {{ >playClipButton }}
{{ /if }} {{ /if }}
{{ #if includeLayoutButton }} {{ #if includeLayoutButton }}
{{ >layoutButton viewportRows=viewportRows viewportColumns=viewportColumns}} {{ >layoutButton }}
{{ /if }} {{ /if }}
</div> </div>
</div> </div>

View File

@ -84,8 +84,8 @@ Template.toolbar.events({
if (!OHIF.viewer.functionList.hasOwnProperty(command)) { if (!OHIF.viewer.functionList.hasOwnProperty(command)) {
return; return;
} }
var viewportIndex = Template.parentData(0).activeViewport.get(); var activeViewport = Session.get('activeViewport');
var element = $('.imageViewerViewport').get(viewportIndex); var element = $('.imageViewerViewport').get(activeViewport);
OHIF.viewer.functionList[command](element); OHIF.viewer.functionList[command](element);
} }
}); });

View File

@ -12,7 +12,7 @@ rerenderViewportWithNewSeries = function(element, data) {
data.viewportIndex = viewportIndex; data.viewportIndex = viewportIndex;
// Update the dictionary of loaded series for the specified viewport // Update the dictionary of loaded series for the specified viewport
OHIF.viewer.imageViewerLoadedSeriesDictionary[viewportIndex] = { OHIF.viewer.loadedSeriesData[viewportIndex] = {
seriesInstanceUid: data.seriesInstanceUid, seriesInstanceUid: data.seriesInstanceUid,
studyInstanceUid: data.studyInstanceUid, studyInstanceUid: data.studyInstanceUid,
currentImageIdIndex: 0 currentImageIdIndex: 0

View File

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

View File

@ -11,9 +11,13 @@ Package.onUse(function (api) {
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');
// ---------- Components ---------- // ---------- Components ----------
// Basic components // Basic components

View File

@ -1,8 +1,105 @@
ViewerData = Session.get('ViewerData') || {}; ViewerData = Session.get('ViewerData') || {};
StudyMetaData = {};
getStudyMetadata = function(studyInstanceUid, doneCallback) {
log.info('worklistStudy getStudyMetadata');
if (StudyMetaData.hasOwnProperty(studyInstanceUid) && StudyMetaData[studyInstanceUid]) {
var study = StudyMetaData[studyInstanceUid];
doneCallback(study);
return;
}
Meteor.call('GetStudyMetadata', studyInstanceUid, function(error, study) {
sortStudy(study);
StudyMetaData[studyInstanceUid] = study;
doneCallback(study);
});
};
switchToTab = function(contentId) {
log.info("Switching to tab: " + contentId);
$('.tabTitle a[data-target="#' + contentId + '"]').tab('show');
$("#viewer").remove();
if (contentId === 'worklistTab') {
document.body.style.overflow = null;
document.body.style.height = null;
document.body.style.minWidth = null;
document.body.style.position = null;
return;
}
// Set active tab
Session.set("activeContentId", contentId);
// Get tab content container
var container = $('.tab-content').find("#" + contentId).get(0);
if (!container) {
return;
}
var studyInstanceUid = ViewerData[contentId].studyInstanceUid;
getStudyMetadata(studyInstanceUid, function(study) {
var data = {
viewportRows: ViewerData[contentId].viewportRows,
viewportColumns: ViewerData[contentId].viewportColumns,
contentId: contentId,
studies: [study],
};
Session.set('studies', data.studies);
UI.renderWithData(Template.viewer, data, container);
var imageViewer = $("#viewer");
if (imageViewer) {
document.body.style.overflow = "hidden";
document.body.style.height = '100%';
document.body.style.width = '100%';
document.body.style.minWidth = 0;
document.body.style.position = 'fixed'; // Prevent overscroll on mobile devices
}
});
};
openNewTab = function(studyInstanceUid) {
getStudyMetadata(studyInstanceUid, function(study) {
var title = study.seriesList[0].instances[0].patientName;
var contentid = generateUUID();
var data = {
title: title,
contentid: contentid,
};
tabs.insert(data);
ViewerData[contentid] = {
title: title,
contentid: contentid,
studyInstanceUid: studyInstanceUid
};
switchToTab(contentid);
});
};
Template.worklist.onRendered(function() {
// If there is a tab set as active in the Session,
// switch to that now.
var contentId = Session.get("activeContentId");
if (contentId) {
switchToTab(contentId);
}
});
Template.worklist.helpers({ Template.worklist.helpers({
'tabs': function() { 'tabs': function() {
console.log('Updating tabs'); log.info('Updating tabs');
return tabs.find(); return tabs.find();
} }
}); });
@ -13,68 +110,3 @@ Template.worklist.events({
switchToTab(contentId); switchToTab(contentId);
} }
}); });
switchToTab = function(contentId) {
var data = ViewerData[contentId];
console.log("Switching to tab: " + contentId);
// Set active tab
Session.set("activeContentId", contentId);
$('.tabTitle a[data-target="#' + contentId + '"]').tab('show');
$("#viewer").remove();
var container = $('.tab-content').find("#" + contentId).get(0);
if (!container) {
return;
}
if (contentId === 'worklistTab') {
console.log('Switching to worklist');
document.body.style.overflow = null;
document.body.style.height = null;
document.body.style.minWidth = null;
document.body.style.position = null;
} else {
UI.renderWithData(Template.viewer, data, container);
var imageViewer = $("#viewer");
if (imageViewer) {
document.body.style.overflow = "hidden";
document.body.style.height = '100%';
document.body.style.width = '100%';
document.body.style.minWidth = 0;
document.body.style.position = 'fixed'; // Prevent overscroll on mobile devices
}
}
};
Template.worklist.onRendered(function() {
this.autorun(function() {
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,5 @@
Template.worklistStudy.events({ Template.worklistStudy.events({
'click': function (){ 'click': function () {
Router.go('viewer', {_id: this.studyInstanceUid}); openNewTab(this.studyInstanceUid);
} }
}); });

3
Packages/worklist/log.js Normal file
View File

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

View File

@ -11,6 +11,13 @@ Package.onUse(function (api) {
api.use('jquery'); api.use('jquery');
api.use('stylus'); api.use('stylus');
api.use('practicalmeteor:loglevel');
// Our custom package
api.use('dicomweb');
api.addFiles('log.js', 'client');
// Components // Components
api.addFiles('components/worklist.html', 'client'); api.addFiles('components/worklist.html', 'client');
api.addFiles('components/worklist.js', 'client'); api.addFiles('components/worklist.js', 'client');
@ -37,6 +44,7 @@ Package.onUse(function (api) {
api.addFiles('lib/generateUUID.js', 'client'); api.addFiles('lib/generateUUID.js', 'client');
api.export('generateUUID', 'client'); api.export('generateUUID', 'client');
api.export('openNewTab', 'client');
api.export('switchToTab', 'client'); api.export('switchToTab', 'client');
api.export('ViewerData', 'client'); api.export('ViewerData', 'client');

View File

@ -1,2 +1,16 @@
# Viewers # Viewers
This repo contains the OHIF DICOM Viewer and Lesion Trackers, and various shared meteor packages This repo contains the OHIF DICOM Viewer and Lesion Trackers, and various shared meteor packages
### Docker usage
#### Temporary data storage
docker run --rm -p 4242:4242 -p 8042:8042 jodogne/orthanc-plugins
#### Persistent data storage
1. Create a persistant data volume for Orthanc to use
docker create --name sampledata -v /sampledata jodogne/orthanc-plugins
2. Run Orthanc from Docker with the data volume attached
docker run --volumes-from sampledata -p 4242:4242 -p 8042:8042 jodogne/orthanc-plugins
3. Upload your data and it will be persisted