Merged LesionTracker and OHIF Viewer, refactored out common components

This commit is contained in:
Erik Ziegler committed 2015-10-13 14:14:50 +02:00
1 parent 34adbe3fbc
commit b774c22593
144 files changed
+27820

No files matched your search

@@ -0,0 +1,33 @@
//Collapse the container
function collapseContainer ( container ) {
container.toggleClass("collapseHorizontal");
}
//function add collapse button
function addButtonCollapse ( container ) {
var btnCollapse = document.createElement("button");
btnCollapse.innerHTML = ">";
btnCollapse.classList.add('btnCollapse');
btnCollapse.onclick = function() { // Note this is a function
collapseContainer(container);
};
container.append(btnCollapse);
}
$.fn.dockingContainer = function(options) {
var self = this;
var opts = $.extend( {}, $.fn.dockingContainer.defaults, options);
//Set container class
this.addClass("dockingContainer");
//Add btnCollapse
addButtonCollapse(this);
return this;
};
//Defaults
$.fn.dockingContainer.defaults = {
background: "black"
};
@@ -0,0 +1,285 @@
var lineIndex = 0; //This holds drawn line index
var cornerstoneTools = (function ($, cornerstone, cornerstoneMath, cornerstoneTools) {
"use strict";
if(cornerstoneTools === undefined) {
cornerstoneTools = {};
}
var toolType = "lesion";
///////// BEGIN ACTIVE TOOL ///////
function createNewMeasurement(mouseEventData)
{
var element = mouseEventData.element;
var lesionCounter = "";
//setting name of lesion
$(element).on("LesionNameSet", function(e,counter){
lesionCounter = counter;
});
//Listens LesionToolModified event and calls measurementModified function when lesion measurement is changed or updated.
$(element).on("LesionToolModified", measurementModified);
$(element).trigger("LesionMeasurementCreated");
// create the measurement data for this tool with the end handle activated
var measurementData = {
visible : true,
active : true,
handles : {
start : {
x : mouseEventData.currentPoints.image.x,
y : mouseEventData.currentPoints.image.y,
highlight: true,
active: false
},
end: {
x : mouseEventData.currentPoints.image.x,
y : mouseEventData.currentPoints.image.y,
highlight: true,
active: true
}
},
index: lineIndex,
measurementText: "",
linkedTextCoords: {
start : {
x : mouseEventData.currentPoints.image.x,
y : mouseEventData.currentPoints.image.y,
highlight: true,
active: false
},
end: {
x : mouseEventData.currentPoints.image.x,
y : mouseEventData.currentPoints.image.y,
highlight: true,
active: true
},
init: false
},
lesionName: "Target "+lesionCounter,
isDeleted: false,
lineNumber:"" //Indicates line number on image
};
lineIndex++;
return measurementData;
}
///////// END ACTIVE TOOL ///////
function pointNearTool(element, data, coords)
{
var lineSegment = {
start: cornerstone.pixelToCanvas(element, data.handles.start), end: cornerstone.pixelToCanvas(element, data.handles.end)
};
var distanceToPoint = cornerstoneMath.lineSegment.distanceToPoint(lineSegment, coords);
return (distanceToPoint < 25);
}
function pointNearToolForText(element, data, coords) {
var lineSegment = {
start: cornerstone.pixelToCanvas(element, data.linkedTextCoords.start), end: cornerstone.pixelToCanvas(element, data.linkedTextCoords.end)
};
var distanceToPoint = cornerstoneMath.lineSegment.distanceToPoint(lineSegment, coords);
return (distanceToPoint < 30);
}
///////// BEGIN IMAGE RENDERING ///////
function onImageRendered(e, eventData) {
// if we have no toolData for this element, return immediately as there is nothing to do
var toolData = cornerstoneTools.getToolState(e.currentTarget, toolType);
if (toolData === undefined) {
return;
}
// we have tool data for this element - iterate over each one and draw it
var context = eventData.canvasContext.canvas.getContext('2d');
context.setTransform(1, 0, 0, 1, 0, 0);
var color;
var lineWidth = cornerstoneTools.toolStyle.getToolWidth();
var font = cornerstoneTools.textStyle.getFont();
var config = cornerstoneTools.length.getConfiguration();
for (var i = 0; i < toolData.data.length; i++) {
context.save();
// configurable shadow
if (config && config.shadow) {
context.shadowColor = '#000000';
context.shadowOffsetX = +1;
context.shadowOffsetY = +1;
}
var data = toolData.data[i];
if (data.active) {
color = cornerstoneTools.toolColors.getActiveColor();
} else {
color = cornerstoneTools.toolColors.getToolColor();
}
// draw the line
var handleStartCanvas = cornerstone.pixelToCanvas(eventData.element, data.handles.start);
var handleEndCanvas = cornerstone.pixelToCanvas(eventData.element, data.handles.end);
context.beginPath();
context.strokeStyle = color;
context.lineWidth = lineWidth;
context.moveTo(handleStartCanvas.x, handleStartCanvas.y);
context.lineTo(handleEndCanvas.x, handleEndCanvas.y);
context.stroke();
// draw the handles
cornerstoneTools.drawHandles(context, eventData, data.handles, color);
//Set coordinates of text
var linkedTextStartCanvas = cornerstone.pixelToCanvas(eventData.element, data.linkedTextCoords.start);
var linkedTextEndCanvas = cornerstone.pixelToCanvas(eventData.element, data.linkedTextCoords.end);
if(!data.linkedTextCoords.init){
data.linkedTextCoords.start.x = data.handles.start.x + 50;
data.linkedTextCoords.start.y = data.handles.start.y + 40;
linkedTextStartCanvas = cornerstone.pixelToCanvas(eventData.element, data.linkedTextCoords.start);
//Set end point of linkedTextCoords
data.linkedTextCoords.end.x = data.linkedTextCoords.start.x + 50;
data.linkedTextCoords.end.y = data.linkedTextCoords.start.y + 30;
linkedTextEndCanvas = cornerstone.pixelToCanvas(eventData.element, data.linkedTextCoords.end);
//initialized coordinates of text
data.linkedTextCoords.init = true;
}
//Draw linked line as dashed
context.setLineDash([2,3]);
context.beginPath();
context.strokeStyle = color;
context.lineWidth = 1 / eventData.viewport.scale;
var mid = {
x:(handleStartCanvas.x + handleEndCanvas.x) / 2, y:(handleStartCanvas.y + handleEndCanvas.y) / 2
};
context.moveTo(mid.x, mid.y);
context.lineTo(linkedTextStartCanvas.x + 20, linkedTextStartCanvas.y + 20);
context.stroke();
// Draw the text
context.fillStyle = color;
context.font = font;
var dx = (data.handles.start.x - data.handles.end.x) * (eventData.image.columnPixelSpacing || 1);
var dy = (data.handles.start.y - data.handles.end.y) * (eventData.image.rowPixelSpacing || 1);
var length = Math.sqrt(dx * dx + dy * dy);
var suffix = ' mm';
if (!eventData.image.rowPixelSpacing || !eventData.image.columnPixelSpacing) {
suffix = ' pixels';
}
var text = '' + length.toFixed(2) + suffix;
var textCoords = {
x: linkedTextStartCanvas.x, y: linkedTextStartCanvas.y
};
cornerstoneTools.drawTextBox(context, data.lesionName, textCoords.x, textCoords.y, color);
cornerstoneTools.drawTextBox(context, text, textCoords.x, textCoords.y + 20, color);
//Set measurement text to show lesion table
data.measurementText = length.toFixed(2);
$(eventData.enabledElement.element).trigger("LesionTextChanged",data);
context.restore();
}
}
///////// END IMAGE RENDERING ///////
//This function is called from cornerstone-viewport.html and updates lesion measurement and makes the lesion active
function measurementModified(e, eventObject){
var start = new Date();
var enabledElement = eventObject.enabledElement;
var lineIndex = eventObject.lineIndex;
var type = eventObject.type;
// if we have no toolData for this element, return immediately as there is nothing to do
var toolData = cornerstoneTools.getToolState(e.currentTarget, toolType);
if (toolData === undefined) {
return;
}
//If type is delete, remove measurement
if(type === "delete") {
var deletedDataIndex = -1;
for (var i = 0; i < toolData.data.length; i++) {
var data = toolData.data[i];
//When click a row of table measurements, measurement will be active and color will be green
if (data.index === eventObject.lineIndex && eventObject.type !== "active") {
data.visible = false;
deletedDataIndex = i;
}
}
if (deletedDataIndex >= 0 && deletedDataIndex < toolData.data.length) {
toolData.data.splice(deletedDataIndex, 1);
}
} else {
for (var i = 0; i < toolData.data.length; i++) {
var data = toolData.data[i];
//When click a row of table measurements, measurement will be active and color will be green
if (data.index === eventObject.lineIndex && eventObject.type === "active") {
data.active = true;
} else{
data.active = false;
}
}
}
enabledElement.image.render(enabledElement, true);
var context = enabledElement.canvas.getContext('2d');
var end = new Date();
var diff = end - start;
//console.log(diff + ' ms');
var eventData = {
viewport : enabledElement.viewport,
element : enabledElement.element,
image : enabledElement.image,
enabledElement : enabledElement,
canvasContext: context,
measurementText: "",
renderTimeInMs : diff,
lineIndex : lineIndex,
type : type //Holds image will be deleted or active
};
enabledElement.invalid = false;
onImageRendered(e, eventData);
}
// module exports
cornerstoneTools.lesion = cornerstoneTools.mouseButtonTool({
createNewMeasurement : createNewMeasurement,
onImageRendered: onImageRendered,
pointNearTool : pointNearTool,
pointNearToolForText: pointNearToolForText,
toolType : toolType
});
cornerstoneTools.lesionTouch = cornerstoneTools.touchTool({
createNewMeasurement: createNewMeasurement,
onImageRendered: onImageRendered,
pointNearTool: pointNearTool,
pointNearToolForText: pointNearToolForText,
toolType: toolType
});
return cornerstoneTools;
}($, cornerstone, cornerstoneMath, cornerstoneTools));
@@ -0,0 +1,15 @@
//Holds functions to help managing tabs
//Returns activeTabId
function activeTabId() {
var activeTab = Session.get("activeTabId");
return activeTab;
}
//Returns activeTabContentId
function activeTabContentId () {
var activeTabId = activeTabId();
var splitTabId = activeTabId.split("tab");
var contentId = "content"+splitTabId[1];
return contentId;
}
@@ -0,0 +1,5 @@
<template name="hidingPanel">
<div class="hidingPanel">
{{> studyBrowser}}
</div>
</template>
@@ -0,0 +1,9 @@
.hidingPanel
width: 12%
height: 100%
margin-top: 35px
position: relative
float: left
overflow: auto
background-color: blue
color: black
@@ -0,0 +1,48 @@
.toolbar {
width: 100%;
height: 35px;
text-align: center;
}
#toolbar-group button {
text-align: center;
margin-top: 2px;
margin-right: 3px;
margin-left: 3px;
color: #ffffff;
background-color: black;
width: 30px;
height: 30px;
border: 1px solid white;
}
#toolbar-group button > span {
width: 12px;
height: 12px;
position: absolute;
top: 9px;
left: 9px;
right: 9px;
bottom: 9px;
}
#toolbar-group .btn:hover {
border-width: 3px;
background-color: transparent;
color: white;
}
#toolbar-group .btn:focus, .btn:active {
outline: 0 !important;
border-width: 5px;
background-color: transparent;
color:white;
text-align: center;
}
#toolbar-group .btn:focus > span{
top: 6px;
left: 6px;
right: 6px;
bottom: 6px;
}
@@ -0,0 +1,36 @@
<template name="toolbar">
<div id="toolbar" class="toolbar">
<div id="toolbar-group" class="btn-group">
<!-- WW/WL -->
<button id="wwwc" type="button" class="imageViewerTool btn btn-sm btn-toolbar" data-container="body" data-toggle="tooltip" data-placement="bottom" title="WW/WC"><span class="fa fa-sun-o"></span></button>
<!-- Invert -->
<button id="invert" type="button" class="imageViewerCommand btn btn-sm btn-toolbar" data-container="body" data-toggle="tooltip" data-placement="bottom" title="Invert"><span class="fa fa-adjust"></span></button>
<!-- Zoom -->
<button id="zoom" type="button" class="imageViewerTool btn btn-sm btn-toolbar" data-container="body" data-toggle="tooltip" data-placement="bottom" title="Zoom"><span class="fa fa-search"></span></button>
<!-- Pan -->
<button id="pan" type="button" class="imageViewerTool btn btn-sm btn-toolbar" data-container="body" data-toggle="tooltip" data-placement="bottom" title="Pan"><span class="fa fa-arrows"></span></button>
<!-- Stack scroll -->
<button id="stackScroll" type="button" class="imageViewerTool btn btn-sm btn-toolbar" data-container="body" data-toggle="tooltip" data-placement="bottom" title="Stack Scroll"><span class="fa fa-bars"></span></button>
<!-- Play clip -->
<button id="playClip" type="button" class="imageViewerCommand btn btn-sm btn-default" data-container="body" data-toggle="tooltip" data-placement="bottom" title="Play/Stop Clip">
{{ #if isPlaying }}
<span class="fa fa-stop"></span>
{{ else }}
<span class="fa fa-play"></span>
{{ /if }}
</button>
<!-- Angle measurement -->
<!--<button id="angle" type="button" class="imageViewerTool btn btn-sm btn-default" data-container="body" data-toggle="tooltip" data-placement="bottom" title="Angle Measurement"><span class="fa fa-angle-left"></span></button>-->
<!-- Pixel probe -->
<button id="dragProbe" type="button" class="imageViewerTool btn btn-sm btn-toolbar" data-container="body" data-toggle="tooltip" data-placement="bottom" title="Pixel Probe"><span class="fa fa-dot-circle-o"></span></button>
<!-- Length measurement -->
<button id="length" type="button" class="imageViewerTool btn btn-sm btn-toolbar" data-container="body" data-toggle="tooltip" data-placement="bottom" title="Length Measurement"><span class="fa fa-arrows-v"></span></button>
<!-- Elliptical ROI -->
<button id="ellipticalRoi" type="button" class="imageViewerTool btn btn-sm btn-toolbar" data-container="body" data-toggle="tooltip" data-placement="bottom" title="Elliptical ROI"><span class="fa fa-circle-o"></span></button>
<!-- Rectangle ROI -->
<button id="rectangleRoi" type="button" class="imageViewerTool btn btn-sm btn-toolbar" data-container="body" data-toggle="tooltip" data-placement="bottom" title="Rectangle ROI"><span class="fa fa-square-o"></span></button>
<!-- Lesion Tool -->
<button id="lesion" type="button" class="imageViewerTool btn btn-sm btn-toolbar" data-container="body" data-toggle="tooltip" data-placement="bottom" title="Lesion Tool"><span class="fa fa-arrows-v"></span></button>
</div>
</div>
</template>
@@ -0,0 +1,34 @@
Template.toolbar.events({
'click .imageViewerTool': function(e) {
var tool = e.currentTarget.id;
console.log('Setting active tool to: ' + tool);
toolManager.setActiveTool(tool);
},
'click .imageViewerCommand': function(e) {
var command = e.currentTarget.id;
if (!OHIF.viewer.functionList.hasOwnProperty(command)) {
return;
}
// TODO = Add support for active viewports
//var element = getActiveViewportElement();
$('.imageViewer').each(function() {
var element = this;
OHIF.viewer.functionList[command](element);
});
}
});
Template.toolbar.onRendered(function() {
var tooltipButtons = $('[data-toggle="tooltip"]');
tooltipButtons.tooltip(OHIF.viewer.tooltipConfig);
// Enable tooltips for the layout button
var extraTooltipButtons = $('[rel="tooltip"]');
extraTooltipButtons.tooltip(OHIF.viewer.tooltipConfig);
toolManager.addTool('lesion', {
mouse: cornerstoneTools.lesion,
touch: cornerstoneTools.lesionTouch
});
});
@@ -0,0 +1,7 @@
.viewer{
width: 100%;
height: 100%;
margin: 0;
padding: 0;
background-color: black;
}
@@ -0,0 +1,9 @@
<template name="viewer">
<head>
<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1,maximum-scale=1,user-scalable=no">
</head>
<div id="viewer" class="viewer">
{{ >hidingPanel }}
{{ >viewerMain }}
</div>
</template>
+89
View File
@@ -0,0 +1,89 @@
OHIF = {
viewer: {}
};
OHIF.viewer.imageViewerLoadedSeriesDictionary = {};
OHIF.viewer.imageViewerCurrentImageIdIndexDictionary = {};
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);
viewport.invert = !viewport.invert;
cornerstone.setViewport(element, viewport);
},
playClip: function(element) {
var viewportIndex = $('.imageViewerViewport').index(element);
var isPlaying = OHIF.viewer.isPlaying[viewportIndex] || false;
if (isPlaying === true) {
cornerstoneTools.stopClip(element);
} else {
cornerstoneTools.playClip(element);
}
OHIF.viewer.isPlaying[viewportIndex] = !OHIF.viewer.isPlaying[viewportIndex];
Session.set('UpdateCINE', Random.id());
}
};
if (isTouchDevice()) {
OHIF.viewer.tooltipConfig = {
trigger: 'manual'
};
} else {
OHIF.viewer.tooltipConfig = {
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);
}
Session.setDefault('ActiveViewport', 0);
Session.setDefault('viewportRows', 1);
Session.setDefault('viewportColumns', 1);
// Avoid doing DOM manipulation during the resize handler
// because it is fired very often.
// Resizing is therefore performed 100 ms after the resize event stops.
var resizeTimer;
$(window).on('resize', function() {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function() {
resizeViewports();
}, 100);
});
Template.viewer.onRendered(function() {
var imageViewer = $("#viewer");
if (imageViewer) {
$('.navbar-default').css({
'background-color': '#000000',
'border-color': '#101010'
});
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
}
});
@@ -0,0 +1,5 @@
.viewerMain {
width: 88%;
height: 100%;
margin-left: 12%;
}
@@ -0,0 +1,7 @@
<template name="viewerMain">
<div class="viewerMain">
{{> toolbar}}
{{> imageViewerViewports}}
<!--TODO: Bottom lesion table container will be here-->
</div>
</template>
+12
View File
@@ -0,0 +1,12 @@
<head>
<meta charset="utf-8">
<title>Lesion Tracker</title>
<meta name="description" content="Prototype viewer application for oncology">
<meta name="viewport" content="user-scalable=no, initial-scale=1, minimal-ui, maximum-scale=1, minimum-scale=1" />
<link rel="shortcut icon" type="image/png" href="images/logo.png" sizes="16x16 32x32 64x64">
<!--Best display for mobile devices-->
<meta http-equiv="cleartype" content="on">
<meta name="MobileOptimized" content="320">
<meta name="HandheldFriendly" content="True">
<meta name="apple-mobile-web-app-capable" content="yes">
</head>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

+34
View File
@@ -0,0 +1,34 @@
Router.configure({
layoutTemplate: 'layout',
//loadingTemplate: '',
notFoundTemplate: 'notFound'
});
Router.route('/', function () {
this.render('worklist', {
});
});
ViewerStudies = new Mongo.Collection(null);
Router.route('/viewer/:_id', {
layoutTemplate: 'layout',
name: 'viewer',
onBeforeAction: function() {
var self = this;
//Session.set('openNewTabEvent', self);
Meteor.call('GetStudyMetadata', this.params._id, function(error, study) {
//console.log(study);
sortStudy(study);
var studies = [study];
Session.set('studies', studies);
//Session.set(activeTabId, studies);
//Session.set('showContentInTab', true);
self.render('viewer');
});
}
});
+5
View File
@@ -0,0 +1,5 @@
Router.onBeforeAction(function () {
// User is logged in, go ahead and route them
this.next();
});
@@ -0,0 +1,52 @@
/*****************************/
/* Toggle */
/*****************************/
* {
box-sizing: border-box;
}
.dockingContainer, .collapseVertical, .collapseHorizontal {
-moz-transition: all 0.3s ease-out;
-webkit-transition: all 0.3s ease-out;
transition: all 0.3s ease-out;
}
.dockingContainer {
-moz-transition: all 0.3s ease-out;
-webkit-transition: all 0.3s ease-out;
transition: all 0.3s ease-out;
}
.collapseVertical {
opacity: 0;
height: 0;
border: none;
}
.collapseHorizontal {
width: 30px;
}
.btnCollapse{
position: absolute;
top: 0;
left: 0;
display: block;
width: 25px;
height: 25px;
line-height: 25px;
padding: 0;
border: 0;
border-top: 1px solid #e6e6e6;
background: #f6f6f6;
color: #666;
font-size: 0.875rem;
text-align: center;
cursor: pointer;
z-index: 100;
}
.btnCollapseIcon-collapse{
-moz-transform: rotate(180deg);
-webkit-transform: rotate(180deg);
transform: rotate(180deg);
}