First pass at a Hanging Protocol implementation and editor (LT-131)

This commit is contained in:
Erik Ziegler committed 2016-05-07 12:40:02 +02:00
1 parent dfcb33153f
commit 2e52d9e3c1
157 files changed
+8869 -23585

No files matched your search

@@ -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);
}
@@ -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();
});
@@ -1,7 +1,7 @@
#imageViewerViewports
height: calc(100% - 30px)
height: 100%
width: 100%
.viewportContainer
float: left
border: 2px solid #777
@@ -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>
@@ -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;
}
});
@@ -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);
}
});
});
@@ -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) {
@@ -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