First pass at a Hanging Protocol implementation and editor (LT-131)
This commit is contained in:
1 parent
dfcb33153f
commit
2e52d9e3c1
157 files changed
+8869
-23585
No files matched your search
+16
@@ -0,0 +1,16 @@
|
||||
<template name="confirmDeleteDialog">
|
||||
<div id="confirmDeleteDialog" tabindex="0">
|
||||
{{ #if title }}
|
||||
<h5>{{title}}</h5>
|
||||
{{ else }}
|
||||
<h5>Remove data from Timepoint?</h5>
|
||||
{{/if}}
|
||||
{{ #if text }}
|
||||
<p>{{text}}</p>
|
||||
{{ else }}
|
||||
<p>Are you sure you would like to remove the measurement data at this timepoint?</p>
|
||||
{{/if}}
|
||||
<button id="cancel" class="btn btn-link" tabindex="1">Cancel</button>
|
||||
<button id="confirm" class="btn btn-primary" tabindex="0">OK</button>
|
||||
</div>
|
||||
</template>
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
function closeHandler() {
|
||||
// Hide the lesion dialog
|
||||
$('#confirmDeleteDialog').css('display', 'none');
|
||||
|
||||
// Remove the backdrop
|
||||
$('.removableBackdrop').remove();
|
||||
|
||||
// Remove the callback from the template data
|
||||
delete Template.confirmDeleteDialog.doneCallback;
|
||||
|
||||
// Restore the focus to the active viewport
|
||||
setFocusToActiveViewport();
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the confirmation dialog template and the removable backdrop element
|
||||
*
|
||||
* @param doneCallback A callback
|
||||
* @param options
|
||||
*/
|
||||
showConfirmDialog = function(doneCallback, options) {
|
||||
// Show the backdrop
|
||||
options = options || {};
|
||||
UI.renderWithData(Template.removableBackdrop, options, document.body);
|
||||
|
||||
var confirmDeleteDialog = $('#confirmDeleteDialog');
|
||||
confirmDeleteDialog.remove();
|
||||
|
||||
var viewer = document.getElementById('viewer');
|
||||
UI.renderWithData(Template.confirmDeleteDialog, options, viewer);
|
||||
|
||||
// Make sure the context menu is closed when the user clicks away
|
||||
$('.removableBackdrop').one('mousedown touchstart', function() {
|
||||
closeHandler();
|
||||
});
|
||||
|
||||
confirmDeleteDialog = $('#confirmDeleteDialog');
|
||||
confirmDeleteDialog.css('display', 'block');
|
||||
confirmDeleteDialog.focus();
|
||||
|
||||
if (doneCallback && typeof doneCallback === 'function') {
|
||||
Template.confirmDeleteDialog.doneCallback = doneCallback;
|
||||
}
|
||||
};
|
||||
|
||||
// Global object of key names (TODO: put this somewhere else)
|
||||
keys = {
|
||||
ESC: 27,
|
||||
ENTER: 13
|
||||
};
|
||||
|
||||
Template.confirmDeleteDialog.events({
|
||||
'click #cancel, click #close': function() {
|
||||
closeHandler();
|
||||
},
|
||||
'click #confirm': function() {
|
||||
var doneCallback = Template.confirmDeleteDialog.doneCallback;
|
||||
if (doneCallback && typeof doneCallback === 'function') {
|
||||
doneCallback();
|
||||
}
|
||||
|
||||
closeHandler();
|
||||
},
|
||||
'keydown #confirmDeleteDialog': function(e) {
|
||||
if (e.which === keys.ESC) {
|
||||
closeHandler();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.keyPressAllowed === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
var doneCallback = Template.confirmDeleteDialog.doneCallback;
|
||||
|
||||
// If Enter is pressed, close the dialog
|
||||
if (e.which === keys.ENTER) {
|
||||
if (doneCallback && typeof doneCallback === 'function') {
|
||||
doneCallback();
|
||||
}
|
||||
|
||||
closeHandler();
|
||||
}
|
||||
}
|
||||
});
|
||||
+12
-11
@@ -1,25 +1,26 @@
|
||||
#progressDialog
|
||||
#confirmDeleteDialog
|
||||
display: none
|
||||
position: absolute
|
||||
top: 0
|
||||
bottom: 0
|
||||
left: 0
|
||||
right: 0
|
||||
margin: auto
|
||||
width: 20%
|
||||
height: 20%
|
||||
z-index: 100
|
||||
width: 300px
|
||||
min-height: 145px
|
||||
max-height: 300px
|
||||
height: fit-content
|
||||
margin: auto
|
||||
border-radius: 5px
|
||||
padding: 10px 20px 10px 20px
|
||||
background-color: rgba(255,255,255,1)
|
||||
outline: none
|
||||
|
||||
.dialogContent
|
||||
margin-bottom: 20px
|
||||
.btn
|
||||
text-decoration: none
|
||||
|
||||
#message
|
||||
width: 100%
|
||||
#cancel
|
||||
float: left
|
||||
|
||||
span
|
||||
display: inline-block;
|
||||
width: 100%
|
||||
#confirm
|
||||
float: right
|
||||
@@ -1,20 +0,0 @@
|
||||
<template name="hidingPanel">
|
||||
<div class="hidingPanel {{#if isPinned}}pinned{{/if}}">
|
||||
<div class="iconContainer">
|
||||
{{#unless isPinned}}
|
||||
<i class="arrowIcon fa fa-angle-double-right"></i>
|
||||
{{/unless}}
|
||||
<button class="btn btn-sm btn-primary btnPin {{#if isPinned}}active{{/if}}">
|
||||
<i class="fa fa-thumb-tack"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="hidingPanelContent">
|
||||
{{#if studyDateIsShown}}
|
||||
{{> studyDateList}}
|
||||
{{/if}}
|
||||
<div class="studyBrowserContainer">
|
||||
{{> studyBrowser}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,123 +0,0 @@
|
||||
|
||||
function pinPanel() {
|
||||
var hidingPanel = $(".hidingPanel");
|
||||
hidingPanel.css({
|
||||
width: '122px'
|
||||
});
|
||||
|
||||
hidingPanel.find('.hidingPanelContent').css({
|
||||
transform: 'translate(130px)'
|
||||
});
|
||||
|
||||
// Calculate newWidth of viewportAndLesionTable
|
||||
var viewerWidth = $('#viewer').width();
|
||||
var newWidth = 100 - 122 / viewerWidth * 100;
|
||||
$("#viewportAndLesionTable").css('width', newWidth + '%');
|
||||
}
|
||||
|
||||
function unpinPanel() {
|
||||
// Calculate newWidth of viewportAndLesionTable
|
||||
$("#viewportAndLesionTable").css('width', '99%');
|
||||
}
|
||||
|
||||
Template.hidingPanel.events({
|
||||
'mouseover div.hidingPanel': function(e, template) {
|
||||
var isPinned = template.isPinned.get();
|
||||
if (isPinned) {
|
||||
return;
|
||||
}
|
||||
|
||||
var hidingPanel = $(e.currentTarget);
|
||||
hidingPanel.css({
|
||||
width: '122px'
|
||||
});
|
||||
|
||||
// Set panel as open
|
||||
template.panelOpen.set(true);
|
||||
|
||||
// Rotate Arrow Icon
|
||||
$('.arrowIcon').addClass('fa-flip-horizontal');
|
||||
|
||||
// Set panel content opacity
|
||||
//hidingPanel.find('.hidingPanelContent').css('opacity', '1');
|
||||
hidingPanel.find('.hidingPanelContent').css({
|
||||
transform: 'translate(130px)'
|
||||
});
|
||||
},
|
||||
|
||||
'mouseout div.hidingPanel': function(e, template) {
|
||||
var isPinned = template.isPinned.get();
|
||||
if (isPinned) {
|
||||
return;
|
||||
}
|
||||
|
||||
var hidingPanel = $(e.currentTarget);
|
||||
hidingPanel.css({
|
||||
width: '1%'
|
||||
});
|
||||
|
||||
// Set panel as closed
|
||||
template.panelOpen.set(false);
|
||||
|
||||
// Rotate Arrow Icon
|
||||
$('.arrowIcon').removeClass('fa-flip-horizontal');
|
||||
|
||||
// Set panel content opacity
|
||||
//hidingPanel.find('.hidingPanelContent').css('opacity', '0');
|
||||
hidingPanel.find('.hidingPanelContent').css({
|
||||
transform: 'translate(-130px)'
|
||||
});
|
||||
},
|
||||
'click button.btnPin': function(e, template) {
|
||||
var isPinned = template.isPinned.get();
|
||||
isPinned = !isPinned;
|
||||
|
||||
template.isPinned.set(isPinned);
|
||||
|
||||
// Set isPanelPinned of ViewerData
|
||||
var contentId = template.data.contentId;
|
||||
ViewerData[contentId].isPanelPinned = isPinned;
|
||||
Session.set('ViewerData', ViewerData);
|
||||
|
||||
if (isPinned) {
|
||||
pinPanel();
|
||||
} else {
|
||||
unpinPanel();
|
||||
}
|
||||
|
||||
resizeViewportElements();
|
||||
}
|
||||
});
|
||||
|
||||
Template.hidingPanel.helpers({
|
||||
studyDateIsShown: function() {
|
||||
return true;
|
||||
},
|
||||
isPinned: function() {
|
||||
return Template.instance().isPinned.get();
|
||||
}
|
||||
});
|
||||
|
||||
Template.hidingPanel.onRendered(function() {
|
||||
|
||||
var contentId = this.data.contentId;
|
||||
var isPanelPinned = ViewerData[contentId].isPanelPinned;
|
||||
if (isPanelPinned) {
|
||||
pinPanel();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
Template.hidingPanel.onCreated(function() {
|
||||
|
||||
this.panelOpen = new ReactiveVar(false);
|
||||
|
||||
var contentId = this.data.contentId;
|
||||
var isPanelPinned = ViewerData[contentId].isPanelPinned;
|
||||
if (!isPanelPinned) {
|
||||
isPanelPinned = false;
|
||||
}
|
||||
this.isPinned = new ReactiveVar(isPanelPinned);
|
||||
|
||||
|
||||
});
|
||||
@@ -1,46 +0,0 @@
|
||||
.hidingPanel
|
||||
width: 1%
|
||||
height: 100%
|
||||
position: absolute
|
||||
left: 0
|
||||
top: 70px
|
||||
bottom: 0
|
||||
z-index: 10
|
||||
background-color: black
|
||||
border: 1px solid #666
|
||||
|
||||
&.pinned
|
||||
border: 1px solid transparent
|
||||
|
||||
.iconContainer
|
||||
width: 100%
|
||||
height: 35px
|
||||
overflow: hidden
|
||||
|
||||
.arrowIcon
|
||||
color: white
|
||||
margin: 0px 3px
|
||||
font-size: 15pt
|
||||
line-height: 35px
|
||||
|
||||
.btnPin
|
||||
float: right
|
||||
margin: 3px
|
||||
outline: none
|
||||
|
||||
.hidingPanelContent
|
||||
position: absolute
|
||||
left: -130px
|
||||
width: 100%
|
||||
height: 100%
|
||||
margin: 1px auto
|
||||
|
||||
.studyBrowserContainer
|
||||
height: calc(100% - 150px)
|
||||
|
||||
.hidingPanel, .arrowIcon
|
||||
-o-transition: all 0.3s ease-out
|
||||
-ms-transition: all 0.3s ease-out
|
||||
-moz-transition: all 0.3s ease-out
|
||||
-webkit-transition: all 0.3s ease-out
|
||||
transition: all 0.3s ease-out
|
||||
@@ -1,33 +0,0 @@
|
||||
<template name="layout">
|
||||
<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">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="/">
|
||||
<img height="30px" src="/images/logo.png">
|
||||
<h4 class="name">Open Health Imaging Foundation</h4>
|
||||
</a>
|
||||
</div>
|
||||
<div id="navbar" class="navbar-collapse collapse">
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<li>
|
||||
<a href="http://www.ohif.org">About</a>
|
||||
</li>
|
||||
<li>
|
||||
<form class="navbar-form">
|
||||
<div class="form-group">
|
||||
{{> loginButtons align="right"}}
|
||||
</div>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
{{> yield}}
|
||||
</template>
|
||||
@@ -1,47 +0,0 @@
|
||||
.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
|
||||
color: #888888
|
||||
background-color: #060606
|
||||
border: none
|
||||
font-family: "Sanchez"
|
||||
color: #C1C1C1
|
||||
|
||||
.navbar-default .navbar-nav > li > a
|
||||
color: #C1C1C1
|
||||
font-weight: bold
|
||||
font-size: 12pt
|
||||
|
||||
.navbar-form
|
||||
height: 50px
|
||||
margin: 0
|
||||
padding: 15px 3px
|
||||
font-weight: bold
|
||||
font-size: 12pt
|
||||
line-height: 20px
|
||||
|
||||
.navbar-brand
|
||||
display: inline-block
|
||||
|
||||
img
|
||||
float: left
|
||||
margin-right: 10px
|
||||
margin-top: -5px
|
||||
|
||||
h4.name
|
||||
float: left
|
||||
margin: 0
|
||||
font-family: "Sanchez"
|
||||
color: #C1C1C1
|
||||
|
||||
@media (min-width: 768px)
|
||||
.navbar
|
||||
border-radius: 0px
|
||||
@@ -1,3 +0,0 @@
|
||||
<template name="login">
|
||||
{{> atForm}}
|
||||
</template>
|
||||
@@ -1,3 +0,0 @@
|
||||
<template name="notFound">
|
||||
Not found
|
||||
</template>
|
||||
@@ -1,17 +0,0 @@
|
||||
<template name="progressDialog">
|
||||
<div id="progressDialog" tabindex="0">
|
||||
<div class="dialogHeader">
|
||||
<h5>{{progressDialogTitle}}</h5>
|
||||
</div>
|
||||
<div class="dialogContent">
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-bar-striped active" role="progressbar" style="width:{{progressStatus}}">
|
||||
{{progressStatus}}
|
||||
</div>
|
||||
</div>
|
||||
<div id="message">
|
||||
{{{progressMessage}}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,100 +0,0 @@
|
||||
progressDialog = {
|
||||
/**
|
||||
* Shows progress dialog
|
||||
* @param {Object} settings - Settings used by progress dialog
|
||||
* @param {string} settings.title
|
||||
* @param {int} settings.numberOfCompleted - The value of progress dialog
|
||||
* @param {int} settings.numberOfTotal - The max value of progress dialog
|
||||
* @param {Object} [settings.messageParams] - Show a message in progress dialog by setting message and messageType
|
||||
* @param {string} [settings.messageParams.message="Progress"] - The message will be shown in the progress dialog
|
||||
* @param {string} [settings.messageParams.messageType="info"] - Sets ui, accepts bootstrap predefined classes such as success, info, warning, danger
|
||||
*/
|
||||
'show': function(settings) {
|
||||
// Set dialog settings
|
||||
Session.set("progressDialogSettings", settings);
|
||||
$('#progressDialog').css('display', 'block');
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the value of the progress dialog
|
||||
* @param {int} numberOfCompleted
|
||||
*/
|
||||
'update': function(numberOfCompleted) {
|
||||
var progressDialogSettings = Session.get("progressDialogSettings");
|
||||
progressDialogSettings.numberOfCompleted = numberOfCompleted;
|
||||
|
||||
Session.set("progressDialogSettings", progressDialogSettings);
|
||||
|
||||
if (progressDialogSettings.numberOfCompleted === progressDialogSettings.numberOfTotal) {
|
||||
progressDialog.close();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Closes the progress dialog
|
||||
*/
|
||||
'close': function() {
|
||||
// Reset progressDialogSettings session
|
||||
resetDialogSettingsSession();
|
||||
|
||||
// Close dialog
|
||||
$('#progressDialog').css('display', 'none');
|
||||
},
|
||||
/**
|
||||
* Shows a message in the progress dialog
|
||||
* @param {Object} messageParams
|
||||
* @param {string} messageParams.message
|
||||
* @param {string} messageParams.messageType
|
||||
*/
|
||||
'setMessage': function(messageParams) {
|
||||
var progressDialogSettings = Session.get("progressDialogSettings");
|
||||
if (!messageParams.messageType || messageParams.messageType == '') {
|
||||
messageParams.messageType = 'info';
|
||||
}
|
||||
progressDialogSettings.messageParams = messageParams;
|
||||
Session.set("progressDialogSettings", progressDialogSettings);
|
||||
}
|
||||
};
|
||||
|
||||
Template.progressDialog.helpers({
|
||||
'progressDialogTitle': function () {
|
||||
if (Session.get("progressDialogSettings") && Session.get("progressDialogSettings").title) {
|
||||
return Session.get("progressDialogSettings").title;
|
||||
}
|
||||
|
||||
return "Progress:";
|
||||
},
|
||||
'progressStatus': function() {
|
||||
var numberOfCompleted = 0;
|
||||
if (Session.get("progressDialogSettings") && Session.get("progressDialogSettings").numberOfCompleted) {
|
||||
numberOfCompleted = Session.get("progressDialogSettings").numberOfCompleted;
|
||||
}
|
||||
|
||||
var numberofTotal = 1;
|
||||
if (Session.get("progressDialogSettings") && Session.get("progressDialogSettings").numberOfTotal) {
|
||||
numberofTotal = Session.get("progressDialogSettings").numberOfTotal;
|
||||
}
|
||||
|
||||
return parseInt((numberOfCompleted / numberofTotal) * 100) + "%";
|
||||
},
|
||||
'progressMessage': function() {
|
||||
var progressDialogSettings = Session.get("progressDialogSettings");
|
||||
var messageParams = progressDialogSettings && progressDialogSettings.messageParams || false;
|
||||
if (messageParams && messageParams.message) {
|
||||
return '<span class="label label-'+messageParams.messageType+'">'+messageParams.message+'</span>';
|
||||
}
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
// Resets progressDialogSettings
|
||||
function resetDialogSettingsSession() {
|
||||
Session.set("progressDialogSettings",
|
||||
{
|
||||
title: 'Progress',
|
||||
numberOfCompleted: 0,
|
||||
numberOfTotal: 0,
|
||||
messageParams: {message: null, messageType: 'info'}
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
<template name="studyNotFound">
|
||||
<div class="studyNotFound">
|
||||
<h5>Study not found</h5>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,6 +0,0 @@
|
||||
.studyNotFound
|
||||
margin: 30px
|
||||
|
||||
h5
|
||||
text-align: center
|
||||
color: #888888
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<template name="relatedStudySelect">
|
||||
<div class="relatedStudySelect">
|
||||
<label>Study:</label>
|
||||
<span class="loading"><i class="fa fa-spinner fa-spin"></i></span>
|
||||
<select id="selectStudyDate">
|
||||
{{#each relatedStudies}}
|
||||
<option value="{{studyInstanceUid}}" selected={{selected}}>{{formatDA studyDate}}: {{studyDescription}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
</template>
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
Template.relatedStudySelect.helpers({
|
||||
/**
|
||||
* Returns an array of studies that are related to the current study by patient ID.
|
||||
* The value for 'selected' for the currently loaded study is set to true, so that
|
||||
* this becomes the current option in the combo box.
|
||||
*
|
||||
* @returns {*} Array of studies that are related to the current study by patient ID
|
||||
*/
|
||||
relatedStudies: function() {
|
||||
// Check which study is currently loaded into the study browser
|
||||
var currentStudyInBrowser = ViewerStudies.findOne({
|
||||
selected: true
|
||||
});
|
||||
|
||||
// Find all studies in the Worklist which have the same patientId as the currently selected study
|
||||
var relatedStudies = WorklistStudies.find({
|
||||
patientId: currentStudyInBrowser.patientId
|
||||
}, {
|
||||
sort: {
|
||||
studyDate: -1
|
||||
}
|
||||
}).fetch();
|
||||
|
||||
// If no Study / Timepoint associated studies exist, just
|
||||
// return the list of loaded studies
|
||||
if (!relatedStudies.length) {
|
||||
return ViewerStudies.find();
|
||||
}
|
||||
|
||||
// Modify the array of related studies so the default option is the currently selected study
|
||||
relatedStudies.forEach(function(study, index) {
|
||||
// If the studyInstanceUid matches that of the current study in the browser,
|
||||
// Set this to 'selected', so that it becomes the default option
|
||||
if (study.studyInstanceUid === currentStudyInBrowser.studyInstanceUid) {
|
||||
relatedStudies[index].selected = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Use this array to populate the combo box
|
||||
return relatedStudies;
|
||||
}
|
||||
});
|
||||
|
||||
Template.relatedStudySelect.events({
|
||||
/**
|
||||
* When the study date selector combo box is changed, we will
|
||||
* hide the select box, temporarily display a loading sign, and grab
|
||||
* the selected study. Once the study has been retrieved it is added
|
||||
* into the ViewerStudies collection and set as selected. This reactively
|
||||
* populated the thumbnail browser.
|
||||
*
|
||||
* @param e The select box change event
|
||||
*/
|
||||
'change select#selectStudyDate': function(e) {
|
||||
var selectBox = $(e.currentTarget);
|
||||
var studyInstanceUid = selectBox.val();
|
||||
|
||||
// Hide the select box
|
||||
selectBox.css('display', 'none');
|
||||
|
||||
// Show the loading indicator
|
||||
var loadingIndicator = selectBox.siblings('.loading');
|
||||
loadingIndicator.css('display', 'block');
|
||||
|
||||
getStudyMetadata(studyInstanceUid, function(study) {
|
||||
sortStudy(study);
|
||||
|
||||
// Hide the loading indicator
|
||||
loadingIndicator.css('display', 'none');
|
||||
|
||||
// Show the select box again
|
||||
selectBox.css('display', 'block');
|
||||
|
||||
// Set "Selected" to false for the entire collection
|
||||
ViewerStudies.update({}, {
|
||||
$set: {
|
||||
selected: false
|
||||
}
|
||||
}, {
|
||||
multi: true
|
||||
});
|
||||
|
||||
// Check if this study already exists in the ViewerStudies collection
|
||||
// of loaded studies. If it does, set it's 'selected' value to true.
|
||||
var existingStudy = ViewerStudies.findOne({
|
||||
studyInstanceUid: studyInstanceUid
|
||||
});
|
||||
|
||||
if (existingStudy) {
|
||||
// Set the current finding in the collection to true
|
||||
ViewerStudies.update(existingStudy._id, {
|
||||
$set: {
|
||||
selected: true
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// If the study does not exist, add the 'selected' key to the object
|
||||
// with the value True, and insert it into the ViewerStudies Collection
|
||||
study.selected = true;
|
||||
ViewerStudies.insert(study);
|
||||
});
|
||||
}
|
||||
});
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
.relatedStudySelect
|
||||
margin: 0 auto 15px
|
||||
text-align: center
|
||||
width: 100px
|
||||
|
||||
#selectStudyDate
|
||||
color: black
|
||||
background-color: #666
|
||||
border-color: #666
|
||||
width: 96%
|
||||
margin: 0 auto
|
||||
|
||||
label
|
||||
font-weight: normal
|
||||
color: #666
|
||||
float: left
|
||||
padding-left: 2%
|
||||
|
||||
.loading
|
||||
display: none
|
||||
color: white
|
||||
text-align: center
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
<div class="studyBrowser">
|
||||
<div class="scrollableStudyThumbnails">
|
||||
{{ #each studies }}
|
||||
{{ >thumbnails }}
|
||||
{{#each thumbnails}}
|
||||
{{ >thumbnailEntry }}
|
||||
{{/each}}
|
||||
{{ /each }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
Template.studyBrowser.helpers({
|
||||
studies : function() {
|
||||
return ViewerStudies.find({selected: true});
|
||||
},
|
||||
thumbnails: function() {
|
||||
var study = this;
|
||||
var stacks = createStacks(study);
|
||||
|
||||
var array = [];
|
||||
stacks.forEach(function(stack, index) {
|
||||
array.push({
|
||||
thumbnailIndex: index,
|
||||
stack: stack
|
||||
});
|
||||
});
|
||||
return array;
|
||||
}
|
||||
});
|
||||
@@ -184,9 +184,10 @@ function thumbnailDragEndHandler(e, target) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var viewportIndex = $('.imageViewerViewport').index(element);
|
||||
|
||||
// Rerender the viewport using the drag and drop data
|
||||
rerenderViewportWithNewSeries(element, OHIF.viewer.dragAndDropData);
|
||||
Session.get('UseHangingProtocol', false);
|
||||
layoutManager.rerenderViewportWithNewSeries(viewportIndex, OHIF.viewer.dragAndDropData);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
<template name="thumbnails">
|
||||
{{#each thumbnails}}
|
||||
{{ >thumbnailEntry }}
|
||||
{{/each}}
|
||||
</template>
|
||||
@@ -1,15 +0,0 @@
|
||||
Template.thumbnails.helpers({
|
||||
thumbnails: function() {
|
||||
var study = this;
|
||||
var stacks = createStacks(study);
|
||||
|
||||
var array = [];
|
||||
stacks.forEach(function(stack, index) {
|
||||
array.push({
|
||||
thumbnailIndex: index,
|
||||
stack: stack
|
||||
});
|
||||
});
|
||||
return array;
|
||||
}
|
||||
});
|
||||
@@ -19,7 +19,6 @@ function updateFramerate(rate) {
|
||||
|
||||
// If the movie is playing, stop/start to update the framerate
|
||||
if (isPlaying()) {
|
||||
var element = getActiveViewportElement();
|
||||
cornerstoneTools.stopClip(element);
|
||||
cornerstoneTools.playClip(element);
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
<template name="imageViewerViewports">
|
||||
<template name="gridLayout">
|
||||
<div id='imageViewerViewports'>
|
||||
{{ #each viewerWindow }}
|
||||
{{ #each viewports }}
|
||||
<div class="viewportContainer" style="height:{{height}}%;width:{{width}}%;">
|
||||
<div class="removable">
|
||||
{{ >imageViewerViewport}}
|
||||
@@ -0,0 +1,38 @@
|
||||
Template.gridLayout.helpers({
|
||||
height: function() {
|
||||
var rows = this.rows || 1;
|
||||
return 100 / rows;
|
||||
},
|
||||
width: function() {
|
||||
var columns = this.columns || 1;
|
||||
return 100 / columns;
|
||||
},
|
||||
viewports: function() {
|
||||
var numViewports = this.rows * this.columns;
|
||||
var viewportData = this.viewportData;
|
||||
var numViewportsWithData = this.viewportData.length;
|
||||
|
||||
if (numViewportsWithData < numViewports) {
|
||||
var difference = numViewports - numViewportsWithData;
|
||||
for (var i = 0; i < difference; i++) {
|
||||
viewportData.push({
|
||||
viewportIndex: numViewportsWithData + i + 1,
|
||||
rows: this.rows,
|
||||
columns: this.columns
|
||||
});
|
||||
}
|
||||
} else if (numViewportsWithData > numViewports) {
|
||||
return viewportData.slice(0, numViewports);
|
||||
}
|
||||
|
||||
return viewportData;
|
||||
}
|
||||
});
|
||||
|
||||
Template.gridLayout.onRendered(function() {
|
||||
OHIF.viewer.updateImageSynchronizer = new cornerstoneTools.Synchronizer('CornerstoneNewImage', cornerstoneTools.updateImageSynchronizer);
|
||||
});
|
||||
|
||||
Template.gridLayout.onDestroyed(function() {
|
||||
OHIF.viewer.updateImageSynchronizer.destroy();
|
||||
});
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
#imageViewerViewports
|
||||
height: calc(100% - 30px)
|
||||
height: 100%
|
||||
width: 100%
|
||||
|
||||
|
||||
.viewportContainer
|
||||
float: left
|
||||
border: 2px solid #777
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
<template name="hangingProtocolButtons">
|
||||
<button id="previousPresentationGroup" type="button"
|
||||
class="imageViewerCommand btn btn-sm btn-default"
|
||||
data-container="body"
|
||||
data-toggle="tooltip"
|
||||
data-placement="bottom"
|
||||
title="Previous presentation group"
|
||||
disabled="{{ #unless isPreviousAvailable }} true {{ /unless }}">
|
||||
<span class="fa fa-step-backward"></span>
|
||||
</button>
|
||||
<button id="nextPresentationGroup" type="button"
|
||||
class="imageViewerCommand btn btn-sm btn-default"
|
||||
data-container="body"
|
||||
data-toggle="tooltip"
|
||||
data-placement="bottom"
|
||||
title="Next presentation group"
|
||||
disabled="{{ #unless isNextAvailable }} true {{ /unless }}">
|
||||
<span class="fa fa-step-forward"></span>
|
||||
</button>
|
||||
</template>
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
Template.hangingProtocolButtons.helpers({
|
||||
isNextAvailable: function() {
|
||||
var presentationGroup = Session.get('WindowManagerPresentationGroup');
|
||||
var numPresentationGroups = WindowManager.getNumPresentationGroups();
|
||||
return presentationGroup < numPresentationGroups;
|
||||
},
|
||||
isPreviousAvailable: function() {
|
||||
var presentationGroup = Session.get('WindowManagerPresentationGroup');
|
||||
return presentationGroup > 1;
|
||||
}
|
||||
});
|
||||
+57
-35
@@ -21,6 +21,7 @@ function loadSeriesIntoViewport(data, templateData) {
|
||||
// The viewport index is often used to store information about a viewport element
|
||||
var element = data.element;
|
||||
var viewportIndex = $('.imageViewerViewport').index(element);
|
||||
layoutManager.viewportData[viewportIndex].viewportIndex = viewportIndex;
|
||||
|
||||
// Get the contentID of the current worklist tab, if the viewport is running
|
||||
// alongside the worklist package
|
||||
@@ -140,15 +141,47 @@ function loadSeriesIntoViewport(data, templateData) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If there is a saved object containing Cornerstone viewport data
|
||||
// (e.g. scale, invert, window settings) in the input data, apply it now.
|
||||
//
|
||||
// Otherwise, display the loaded image in the viewport element with the
|
||||
// default viewport settings.
|
||||
if (data.viewport) {
|
||||
// Update the enabled element with the image and viewport data
|
||||
// This is not usually necessary, but we need them stored in case
|
||||
// a sopClassUid-specific viewport setting is present.
|
||||
enabledElement.image = image;
|
||||
enabledElement.viewport = cornerstone.getDefaultViewport(enabledElement.canvas, image);
|
||||
|
||||
// Check if there are default viewport settings for this sopClassUid
|
||||
if (!series.instances || !series.instances.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var instance = series.instances[0];
|
||||
var instanceClassViewport = getInstanceClassDefaultViewport(instance, enabledElement, image.imageId);
|
||||
|
||||
// If there are sopClassUid-specific viewport settings, apply them
|
||||
if (instanceClassViewport) {
|
||||
cornerstone.displayImage(element, image, instanceClassViewport);
|
||||
|
||||
// Mark that this element should not be fit to the window in the resize listeners
|
||||
// TODO: Find another way to do this?
|
||||
enabledElement.fitToWindow = false;
|
||||
|
||||
// Resize the canvas to fit the current viewport element size.
|
||||
cornerstone.resize(element, false);
|
||||
} else if (data.viewport) {
|
||||
// If there is a saved object containing Cornerstone viewport data
|
||||
// (e.g. scale, invert, window settings) in the input data, apply it now.
|
||||
cornerstone.displayImage(element, image, data.viewport);
|
||||
|
||||
// Resize the canvas to fit the current viewport element size. Fit the displayed
|
||||
// image to the canvas dimensions.
|
||||
cornerstone.resize(element, true);
|
||||
} else {
|
||||
// If no saved viewport settings or modality-specific settings exists,
|
||||
// display the loaded image in the viewport element with no loaded viewport
|
||||
// settings.
|
||||
cornerstone.displayImage(element, image);
|
||||
|
||||
// Resize the canvas to fit the current viewport element size. Fit the displayed
|
||||
// image to the canvas dimensions.
|
||||
cornerstone.resize(element, true);
|
||||
}
|
||||
|
||||
// Remove the data for this viewport from the ViewportLoading object
|
||||
@@ -159,10 +192,6 @@ function loadSeriesIntoViewport(data, templateData) {
|
||||
// (e.g. hide the progress text box)
|
||||
endLoadingHandler(element);
|
||||
|
||||
// Resize the canvas to fit the current viewport element size. Fit the displayed
|
||||
// image to the canvas dimensions.
|
||||
cornerstone.resize(element, true);
|
||||
|
||||
// Remove the 'empty' class from the viewport to hide any instruction text
|
||||
element.classList.remove('empty');
|
||||
|
||||
@@ -172,7 +201,7 @@ function loadSeriesIntoViewport(data, templateData) {
|
||||
$(element).siblings('.imageViewerViewportOverlay').show();
|
||||
|
||||
// Add stack state managers for the stack tool, CINE tool, and reference lines
|
||||
cornerstoneTools.addStackStateManager(element, [ 'stack', 'playClip', 'referenceLines' ]);
|
||||
cornerstoneTools.addStackStateManager(element, ['stack', 'playClip', 'referenceLines']);
|
||||
|
||||
// Get the current viewport settings
|
||||
var viewport = cornerstone.getViewport(element);
|
||||
@@ -206,7 +235,7 @@ function loadSeriesIntoViewport(data, templateData) {
|
||||
// Use the tool manager to enable the currently active tool for this
|
||||
// newly rendered element
|
||||
var activeTool = toolManager.getActiveTool();
|
||||
toolManager.setActiveTool(activeTool, [ element ]);
|
||||
toolManager.setActiveTool(activeTool, [element]);
|
||||
|
||||
// Define a function to run whenever the Cornerstone viewport is rendered
|
||||
// (e.g. following a change of window or zoom)
|
||||
@@ -220,6 +249,7 @@ function loadSeriesIntoViewport(data, templateData) {
|
||||
// Save the current viewport into the ViewerData global variable, as well as the
|
||||
// Meteor Session. This lets the viewport be saved/reloaded on a hot-code reload
|
||||
var viewport = cornerstone.getViewport(element);
|
||||
layoutManager.viewportData[viewportIndex].viewport = viewport;
|
||||
ViewerData[contentId].loadedSeriesData[viewportIndex].viewport = viewport;
|
||||
Session.set('ViewerData', ViewerData);
|
||||
}
|
||||
@@ -240,6 +270,7 @@ function loadSeriesIntoViewport(data, templateData) {
|
||||
// This allows the template helpers to update reactively
|
||||
templateData.imageId = eventData.enabledElement.image.imageId;
|
||||
Session.set('CornerstoneNewImage' + viewportIndex, Random.id());
|
||||
layoutManager.viewportData[viewportIndex].imageId = eventData.enabledElement.image.imageId;
|
||||
|
||||
// Get the element and stack data
|
||||
var element = e.target;
|
||||
@@ -260,6 +291,7 @@ function loadSeriesIntoViewport(data, templateData) {
|
||||
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);
|
||||
layoutManager.viewportData[viewportIndex].currentImageIdIndex = imageIdIndex;
|
||||
ViewerData[contentId].loadedSeriesData[viewportIndex].currentImageIdIndex = imageIdIndex;
|
||||
Session.set('ViewerData', ViewerData);
|
||||
}
|
||||
@@ -327,14 +359,17 @@ function loadSeriesIntoViewport(data, templateData) {
|
||||
$(element).off(allCornerstoneEvents, sendActivationTrigger);
|
||||
$(element).on(allCornerstoneEvents, sendActivationTrigger);
|
||||
|
||||
// Store the current series data inside a global variable
|
||||
OHIF.viewer.loadedSeriesData[viewportIndex] = {
|
||||
// Store the current series data inside the Layout Manager
|
||||
layoutManager.viewportData[viewportIndex] = {
|
||||
imageId: imageId,
|
||||
studyInstanceUid: data.studyInstanceUid,
|
||||
seriesInstanceUid: data.seriesInstanceUid,
|
||||
currentImageIdIndex: data.currentImageIdIndex,
|
||||
viewport: viewport
|
||||
viewport: viewport,
|
||||
viewportIndex: viewportIndex
|
||||
};
|
||||
ViewerData[contentId].loadedSeriesData = OHIF.viewer.loadedSeriesData;
|
||||
|
||||
ViewerData[contentId].loadedSeriesData = layoutManager.viewportData;
|
||||
|
||||
// Check if image plane (orientation / loction) data is present for the current image
|
||||
var imagePlane = cornerstoneTools.metaData.get('imagePlane', image.imageId);
|
||||
@@ -443,7 +478,7 @@ Meteor.startup(function() {
|
||||
});
|
||||
|
||||
Template.imageViewerViewport.onCreated(function() {
|
||||
log.info('imageViewerViewport onCreated');
|
||||
log.info('imageViewerViewport onCreated');
|
||||
});
|
||||
|
||||
Template.imageViewerViewport.onRendered(function() {
|
||||
@@ -487,23 +522,6 @@ Template.imageViewerViewport.onRendered(function() {
|
||||
});
|
||||
var seriesInstanceUid = this.data.seriesInstanceUid;
|
||||
|
||||
// TODO: This code block might be refactored
|
||||
// Load previous measurement study when reloading a patient
|
||||
if (!study) {
|
||||
getStudyMetadata(this.data.studyInstanceUid, function(study) {
|
||||
// Once we have retrieved the data, we sort the series' by series
|
||||
// and instance number in ascending order
|
||||
if (!study) {
|
||||
return;
|
||||
}
|
||||
|
||||
sortStudy(study);
|
||||
data.study = study;
|
||||
|
||||
setSeries(data, seriesInstanceUid, templateData);
|
||||
})
|
||||
}
|
||||
|
||||
data.study = study;
|
||||
setSeries(data, seriesInstanceUid, templateData);
|
||||
});
|
||||
@@ -539,5 +557,9 @@ Template.imageViewerViewport.events({
|
||||
'click .imageViewerViewport': function(e) {
|
||||
var viewportIndex = $('.imageViewerViewport').index(e.currentTarget);
|
||||
Session.set('activeViewport', viewportIndex);
|
||||
},
|
||||
'dblclick .imageViewerViewport': function(e) {
|
||||
var viewportIndex = $('.imageViewerViewport').index(e.currentTarget);
|
||||
layoutManager.toggleEnlargement(viewportIndex);
|
||||
}
|
||||
});
|
||||
});
|
||||
-201
@@ -1,201 +0,0 @@
|
||||
ViewerWindows = new Meteor.Collection(null);
|
||||
ViewerWindows._debugName = 'ViewerWindows';
|
||||
|
||||
Template.imageViewerViewports.helpers({
|
||||
height: function() {
|
||||
var viewportRows = this.viewportRows || 1;
|
||||
return 100 / viewportRows;
|
||||
},
|
||||
width: function() {
|
||||
var viewportColumns = this.viewportColumns || 1;
|
||||
return 100 / viewportColumns;
|
||||
},
|
||||
viewerWindow: function() {
|
||||
log.info('ViewerWindows');
|
||||
//log.info(ViewerWindows.find().fetch());
|
||||
ViewerWindows.remove({});
|
||||
|
||||
log.info('imageViewerViewports viewportArray');
|
||||
|
||||
var viewportRows = this.viewportRows || 1;
|
||||
var viewportColumns = this.viewportColumns || 1;
|
||||
|
||||
var contentId = Session.get('activeContentId');
|
||||
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 viewportData;
|
||||
if (!$.isEmptyObject(ViewerData[contentId].loadedSeriesData)) {
|
||||
viewportData = ViewerData[contentId].loadedSeriesData;
|
||||
}
|
||||
|
||||
var inputData = {
|
||||
viewportColumns: viewportColumns,
|
||||
viewportRows: viewportRows
|
||||
};
|
||||
|
||||
inputData.DisplaySetPresentationGroup = Session.get('WindowManagerPresentationGroup');
|
||||
var hangingProtocolViewportData = WindowManager.getHangingProtocol(inputData);
|
||||
if (Session.get('UseHangingProtocol')) {
|
||||
viewportData = hangingProtocolViewportData.viewports;
|
||||
viewportRows = hangingProtocolViewportData.viewportRows || viewportRows;
|
||||
viewportColumns = hangingProtocolViewportData.viewportColumns || viewportColumns;
|
||||
}
|
||||
|
||||
// Update viewerData
|
||||
ViewerData[contentId].viewportRows = viewportRows;
|
||||
ViewerData[contentId].viewportColumns = viewportColumns;
|
||||
Session.set('ViewerData', ViewerData);
|
||||
|
||||
this.viewportRows = viewportRows;
|
||||
this.viewportColumns = viewportColumns;
|
||||
|
||||
var numViewports = viewportRows * viewportColumns;
|
||||
for (var i = 0; i < numViewports; ++i) {
|
||||
var data = {
|
||||
viewportIndex: i,
|
||||
// These two are necessary because otherwise the width and height helpers
|
||||
// don't get the right data context. Seems to be related to the "each" loop.
|
||||
viewportColumns: viewportColumns,
|
||||
viewportRows: viewportRows
|
||||
};
|
||||
|
||||
if (viewportData && !$.isEmptyObject(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 && !$.isEmptyObject(hangingProtocolViewportData.viewports[i])) {
|
||||
data.seriesInstanceUid = hangingProtocolViewportData.viewports[i].seriesInstanceUid;
|
||||
data.studyInstanceUid = hangingProtocolViewportData.viewports[i].studyInstanceUid;
|
||||
data.currentImageIdIndex = hangingProtocolViewportData.viewports[i].currentImageIdIndex;
|
||||
data.viewport = hangingProtocolViewportData.viewports[i].viewport;
|
||||
}
|
||||
|
||||
ViewerWindows.insert(data);
|
||||
}
|
||||
|
||||
// Here we will find out if we need to load any other studies into the viewer
|
||||
|
||||
// We will make a list of unique studyInstanceUids
|
||||
var uniqueStudyInstanceUids = [];
|
||||
|
||||
// Meteor doesn't support Mongo's 'distinct' function, so we have to do this in a loop
|
||||
var windows = ViewerWindows.find({}, {
|
||||
reactive: false
|
||||
}).fetch();
|
||||
|
||||
windows.forEach(function(window) {
|
||||
var studyInstanceUid = window.studyInstanceUid;
|
||||
if (!studyInstanceUid) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If this studyInstanceUid is already in the list, stop here
|
||||
if (uniqueStudyInstanceUids.indexOf(studyInstanceUid) > -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, add it to the list
|
||||
uniqueStudyInstanceUids.push(studyInstanceUid);
|
||||
|
||||
// If any of the associated studies is not already loaded, load it now
|
||||
var loadedStudy = ViewerStudies.findOne({
|
||||
studyInstanceUid: studyInstanceUid
|
||||
}, {
|
||||
reactive: false
|
||||
});
|
||||
|
||||
if (!loadedStudy) {
|
||||
// Load the study
|
||||
getStudyMetadata(studyInstanceUid, function(study) {
|
||||
log.info("imageViewerViewports GetStudyMetadata: " + studyInstanceUid);
|
||||
|
||||
// Sort the study's series and instances by series and instance number
|
||||
sortStudy(study);
|
||||
|
||||
// Insert it into the ViewerStudies Collection
|
||||
ViewerStudies.insert(study);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return ViewerWindows.find({}, {
|
||||
reactive: false
|
||||
}).fetch();
|
||||
}
|
||||
});
|
||||
|
||||
var savedSeriesData,
|
||||
savedViewportRows,
|
||||
savedViewportColumns;
|
||||
|
||||
Template.imageViewerViewports.events({
|
||||
'CornerstoneMouseDoubleClick .imageViewerViewport': function(e) {
|
||||
var container = $('.viewerMain').get(0);
|
||||
var data;
|
||||
var contentId = this.contentId || $('#viewer').parents('.tab-pane.active').attr('id');
|
||||
|
||||
// If there is more than one viewport on screen
|
||||
// And one of them is double-clicked, it should be rendered alone
|
||||
// If it is double-clicked again, the viewer should revert to the previous layout
|
||||
if ($(e.currentTarget).hasClass('zoomed')) {
|
||||
// Revert to saved settings
|
||||
ViewerData[contentId].loadedSeriesData = $.extend(true, {}, savedSeriesData);
|
||||
ViewerData[contentId].viewportRows = savedViewportRows;
|
||||
ViewerData[contentId].viewportColumns = savedViewportColumns;
|
||||
|
||||
savedViewportRows = 0;
|
||||
savedViewportColumns = 0;
|
||||
|
||||
data = {
|
||||
viewportRows: ViewerData[contentId].viewportRows,
|
||||
viewportColumns: ViewerData[contentId].viewportColumns
|
||||
};
|
||||
|
||||
// Render the imageViewerViewports template with these settings
|
||||
$('#imageViewerViewports').remove();
|
||||
UI.renderWithData(Template.imageViewerViewports, data, container);
|
||||
|
||||
// Remove the 'zoomed' class from any viewports
|
||||
$('.imageViewerViewport').removeClass('zoomed');
|
||||
} else {
|
||||
// Zoom to single viewport
|
||||
|
||||
// If only one viewport is on-screen, stop here
|
||||
if (ViewerData[contentId].viewportRows === 1 &&
|
||||
ViewerData[contentId].viewportColumns === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Save the current settings
|
||||
savedSeriesData = $.extend(true, {}, ViewerData[contentId].loadedSeriesData);
|
||||
savedViewportRows = ViewerData[contentId].viewportRows;
|
||||
savedViewportColumns = ViewerData[contentId].viewportColumns;
|
||||
|
||||
// Get the clicked-on viewport's index
|
||||
var viewportIndex = this.viewportIndex;
|
||||
|
||||
// Set the first viewport's data to be the same as the currently clicked-on viewport
|
||||
ViewerData[contentId].loadedSeriesData[0] = ViewerData[contentId].loadedSeriesData[viewportIndex];
|
||||
|
||||
// Set the basic template data
|
||||
data = {
|
||||
viewportRows: 1,
|
||||
viewportColumns: 1
|
||||
};
|
||||
|
||||
// Render the imageViewerViewports template with these settings
|
||||
$('#imageViewerViewports').remove();
|
||||
UI.renderWithData(Template.imageViewerViewports, data, container);
|
||||
|
||||
// Add the 'zoomed' class to the lone remaining viewport
|
||||
$('.imageViewerViewport').eq(0).addClass('zoomed');
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1,6 +0,0 @@
|
||||
<template name="layoutButton">
|
||||
<button id="layout" type="button" class="btn btn-sm btn-default dropdown-toggle" data-container="body" data-toggle="dropdown" aria-expanded="false" data-placement="right" title="Layout" rel="tooltip">
|
||||
<span class="fa fa-th-large"></span>
|
||||
</button>
|
||||
{{ >layoutChooser }}
|
||||
</template>
|
||||
@@ -1,20 +1,29 @@
|
||||
<template name="layoutChooser">
|
||||
<div class="layoutChooser pull-right dropdown-menu" role="menu">
|
||||
<div class="layoutChooser pull-left dropdown-menu" role="menu">
|
||||
<table>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -33,19 +33,18 @@ Template.layoutChooser.events({
|
||||
highlightCells(evt.currentTarget);
|
||||
},
|
||||
'click .layoutChooser table td': function(evt) {
|
||||
$('#imageViewerViewports').remove();
|
||||
|
||||
var currentCell = $(evt.currentTarget);
|
||||
var rowIndex = currentCell.closest('tr').index();
|
||||
var columnIndex = currentCell.index();
|
||||
|
||||
// Add 1 because the indices start from zero
|
||||
var data = {
|
||||
viewportRows: rowIndex + 1,
|
||||
viewportColumns: columnIndex + 1,
|
||||
var layoutProps = {
|
||||
rows: rowIndex + 1,
|
||||
columns: columnIndex + 1
|
||||
};
|
||||
|
||||
var container = $(".viewerMain").get(0);
|
||||
UI.renderWithData(Template.imageViewerViewports, data, container);
|
||||
|
||||
layoutManager.layoutTemplateName = 'gridLayout';
|
||||
layoutManager.layoutProps = layoutProps;
|
||||
layoutManager.updateViewports();
|
||||
}
|
||||
});
|
||||
@@ -1,7 +1,8 @@
|
||||
.layoutChooser
|
||||
width: 75px
|
||||
min-width: 75px // to override bootstrap's dropdown-menu class
|
||||
height: 75px
|
||||
width: 92px
|
||||
height: 92px
|
||||
min-width: 92px // to override bootstrap's dropdown-menu class
|
||||
min-height: 92px
|
||||
|
||||
table
|
||||
margin: 0 auto
|
||||
@@ -11,7 +12,7 @@
|
||||
border: 2px solid black
|
||||
width: 20px
|
||||
height: 20px
|
||||
transition: background-color 0.1s linear
|
||||
transition: background-color 0.1s ease
|
||||
|
||||
&:hover, &.hover // Add the hover class here to be triggered by mouseenter/mouseleave
|
||||
background-color: gray
|
||||
@@ -10,11 +10,17 @@
|
||||
{{ /if }}
|
||||
|
||||
{{ #if includeLayoutButton }}
|
||||
{{ >layoutButton }}
|
||||
<div class="btn-group">
|
||||
<button id="layout" type="button" class="btn btn-sm btn-default dropdown-toggle" data-container="body" data-toggle="dropdown" aria-expanded="false" data-placement="right" title="Layout" rel="tooltip">
|
||||
<span class="fa fa-th-large"></span>
|
||||
</button>
|
||||
{{ >layoutChooser }}
|
||||
</div>
|
||||
{{ /if }}
|
||||
|
||||
{{ #if includeHangingProtocolButtons }}
|
||||
{{ >hangingProtocolButtons }}
|
||||
{{ >matchedProtocols }}
|
||||
{{ /if }}
|
||||
|
||||
{{ #each btnGroup }}
|
||||
|
||||
@@ -152,7 +152,7 @@ Template.toolbar.onRendered(function() {
|
||||
var states = toolManager.getToolDefaultStates();
|
||||
var disabledToolButtons = states.disabledToolButtons;
|
||||
var allToolbarButtons = $('#toolbar').find('button');
|
||||
if (disabledToolButtons.length > 0) {
|
||||
if (disabledToolButtons && disabledToolButtons.length > 0) {
|
||||
for (var i = 0; i < allToolbarButtons.length; i++) {
|
||||
var toolbarButton = allToolbarButtons[i];
|
||||
$(toolbarButton).prop('disabled', false);
|
||||
@@ -191,7 +191,7 @@ Template.toolbar.helpers({
|
||||
return this.toolbarOptions.includeHangingProtocolButtons;
|
||||
}
|
||||
|
||||
return false;
|
||||
return true;
|
||||
},
|
||||
btnGroup: function() {
|
||||
if (this.toolbarOptions && this.toolbarOptions.btnGroup !== undefined) {
|
||||
|
||||
+4
-3
@@ -22,7 +22,7 @@ updateOrientationMarkers = function(element) {
|
||||
|
||||
var markers = {
|
||||
top: oppositeColumnString,
|
||||
left: oppositeRowString,
|
||||
left: oppositeRowString
|
||||
};
|
||||
|
||||
// If any vertical or horizontal flips are applied, change the orientation strings ahead of
|
||||
@@ -30,12 +30,13 @@ updateOrientationMarkers = function(element) {
|
||||
if (viewport.vflip) {
|
||||
markers.top = cornerstoneTools.orientation.invertOrientationString(markers.top);
|
||||
}
|
||||
|
||||
if (viewport.hflip) {
|
||||
markers.left = cornerstoneTools.orientation.invertOrientationString(markers.left);
|
||||
}
|
||||
|
||||
// Get the viewport orientation marker DOM elements
|
||||
var viewportOrientationMarkers = $(element).siblings(".viewportOrientationMarkers");
|
||||
var viewportOrientationMarkers = $(element).siblings('.viewportOrientationMarkers');
|
||||
var topMarker = viewportOrientationMarkers.find('.topMid');
|
||||
var leftMarker = viewportOrientationMarkers.find('.leftMid');
|
||||
|
||||
@@ -54,4 +55,4 @@ updateOrientationMarkers = function(element) {
|
||||
topMarker.text(markers.top);
|
||||
leftMarker.text(markers.left);
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
<template name="viewportOverlay">
|
||||
<div class="imageViewerViewportOverlay noselect">
|
||||
{{ #unless tagDisplaySpecified }}
|
||||
<div class="topleft dicomTag">
|
||||
<div>{{formatPN patientName}}</div>
|
||||
<div>{{patientId}}</div>
|
||||
@@ -21,6 +22,47 @@
|
||||
<div>{{imageDimensions}}</div>
|
||||
<div>{{seriesDescription}}</div>
|
||||
</div>
|
||||
{{ /unless }}
|
||||
|
||||
{{ #if tagDisplayRightOnly }}
|
||||
<div class="topright dicomTag">
|
||||
<div>{{formatPN patientName}}</div>
|
||||
<div>{{patientId}}</div>
|
||||
<div class='priorIndicator'>{{prior}}</div>
|
||||
<div>{{studyDescription}}</div>
|
||||
<div>{{formatDA studyDate}} {{formatTM studyTime}}</div>
|
||||
</div>
|
||||
<div class="bottomright dicomTag">
|
||||
<div>Ser: {{seriesNumber}}</div>
|
||||
<div>Img: {{imageNumber}} ({{imageIndex}}/{{numImages}})</div>
|
||||
<div>{{#if frameRate}}{{frameRate}} FPS{{/if}}</div>
|
||||
<div>{{imageDimensions}}</div>
|
||||
<div>{{seriesDescription}}</div>
|
||||
<div>Zoom: {{formatNumberPrecision zoom 2}}%</div>
|
||||
<div>{{compression}}</div>
|
||||
<div>{{wwwc}}</div>
|
||||
</div>
|
||||
{{ /if }}
|
||||
|
||||
{{ #if tagDisplayLeftOnly }}
|
||||
<div class="topleft dicomTag">
|
||||
<div>{{formatPN patientName}}</div>
|
||||
<div>{{patientId}}</div>
|
||||
<div class='priorIndicator'>{{prior}}</div>
|
||||
<div>{{studyDescription}}</div>
|
||||
<div>{{formatDA studyDate}} {{formatTM studyTime}}</div>
|
||||
</div>
|
||||
<div class="bottomleft dicomTag">
|
||||
<div>Ser: {{seriesNumber}}</div>
|
||||
<div>Img: {{imageNumber}} ({{imageIndex}}/{{numImages}})</div>
|
||||
<div>{{#if frameRate}}{{frameRate}} FPS{{/if}}</div>
|
||||
<div>{{imageDimensions}}</div>
|
||||
<div>{{seriesDescription}}</div>
|
||||
<div>Zoom: {{formatNumberPrecision zoom 2}}%</div>
|
||||
<div>{{compression}}</div>
|
||||
<div>{{wwwc}}</div>
|
||||
</div>
|
||||
{{ /if }}
|
||||
{{>imageControls}}
|
||||
</div>
|
||||
</template>
|
||||
@@ -80,6 +80,20 @@ function getInstance(property) {
|
||||
return instance[property];
|
||||
}
|
||||
|
||||
function getTagDisplay(property) {
|
||||
Session.get('CornerstoneNewImage' + this.viewportIndex);
|
||||
if (!this.imageId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var instance = cornerstoneTools.metaData.get('tagDisplay', this.imageId);
|
||||
if (!instance) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return instance[property];
|
||||
}
|
||||
|
||||
function getImage(viewportIndex) {
|
||||
var element = getElementIfNotEmpty(viewportIndex);
|
||||
if (!element) {
|
||||
@@ -172,6 +186,15 @@ Template.viewportOverlay.helpers({
|
||||
imageNumber: function() {
|
||||
return getInstance.call(this, 'instanceNumber');
|
||||
},
|
||||
tagDisplayLeftOnly: function() {
|
||||
return getTagDisplay.call(this, 'side') === 'L';
|
||||
},
|
||||
tagDisplayRightOnly: function() {
|
||||
return getTagDisplay.call(this, 'side') === 'R';
|
||||
},
|
||||
tagDisplaySpecified: function() {
|
||||
return getTagDisplay.call(this, 'side');
|
||||
},
|
||||
imageIndex: function() {
|
||||
return getInstance.call(this, 'index');
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.imageViewerViewportOverlay
|
||||
font-family: Helvetica, OpenSans
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif
|
||||
font-size: 13px
|
||||
color: #979797
|
||||
line-height: 18px
|
||||
|
||||
Reference in new issue
Block a user