Most JS files, all helpers and simple reconciliations

This commit is contained in:
Emanuel F. Oliveira 2017-01-16 15:30:54 -02:00
parent 21400c5836
commit 9e69c43dda
58 changed files with 776 additions and 1025 deletions

View File

@ -1,5 +1,6 @@
import { Template } from 'meteor/templating';
import { Session } from 'meteor/session';
import { Viewerbase } from 'meteor/ohif:viewerbase';
Template.toolbarSection.helpers({
// Returns true if the view shall be split in two viewports
@ -141,7 +142,7 @@ Template.toolbarSection.helpers({
title: 'CINE',
classes: 'imageViewerCommand',
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-cineplay-toggle',
disableFunction: hasMultipleFrames
disableFunction: Viewerbase.viewportUtils.hasMultipleFrames
});
const buttonData = [];
@ -168,11 +169,11 @@ Template.toolbarSection.helpers({
});
buttonData.push({
id: 'link',
id: 'linkStackScroll',
title: 'Link',
classes: 'imageViewerCommand toolbarSectionButton nonAutoDisableState',
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-link',
disableFunction: isStackScrollLinkingDisabled
disableFunction: Viewerbase.viewportUtils.isStackScrollLinkingDisabled
});
buttonData.push({

View File

@ -2,6 +2,7 @@ import { Template } from 'meteor/templating';
import { Session } from 'meteor/session';
import { ReactiveDict } from 'meteor/reactive-dict';
import { OHIF } from 'meteor/ohif:core';
import 'meteor/ohif:viewerbase';
import { _ } from 'meteor/underscore';
Session.set('ViewerMainReady', false);
@ -32,15 +33,15 @@ Template.viewer.onCreated(() => {
toolManager.setActiveTool('nonTarget');
},
// Viewport functions
toggleCineDialog,
clearTools,
resetViewport,
invert,
flipV,
flipH,
rotateL,
rotateR,
link
toggleCineDialog: OHIF.viewerbase.viewportUtils.toggleCineDialog,
clearTools: OHIF.viewerbase.viewportUtils.clearTools,
resetViewport: OHIF.viewerbase.viewportUtils.resetViewport,
invert: OHIF.viewerbase.viewportUtils.invert,
flipV: OHIF.viewerbase.viewportUtils.flipV,
flipH: OHIF.viewerbase.viewportUtils.flipH,
rotateL: OHIF.viewerbase.viewportUtils.rotateL,
rotateR: OHIF.viewerbase.viewportUtils.rotateR,
linkStackScroll: OHIF.viewerbase.viewportUtils.linkStackScroll
});
if (ViewerData[contentId].loadedSeriesData) {

View File

@ -1,9 +1,16 @@
import { Template } from 'meteor/templating';
import { Session } from 'meteor/session';
import { Random } from 'meteor/random';
import { OHIF } from 'meteor/ohif:core';
import 'meteor/ohif:viewerbase';
/**
* Extend the Array prototype with a Swap function
* so we can swap stages more easily
*/
Array.prototype.move = function(oldIndex, newIndex) {
var value = this[oldIndex];
const value = this[oldIndex];
newIndex = Math.max(0, newIndex);
newIndex = Math.min(this.length, newIndex);
@ -22,13 +29,13 @@ Array.prototype.move = function(oldIndex, newIndex) {
* @returns {number} The index of the specified stage within the Protocol,
* or undefined if it is not present.
*/
function getStageIndex(protocol, id) {
var stageIndex;
const getStageIndex = (protocol, id) => {
let stageIndex;
if (!protocol || !protocol.stages) {
return;
}
protocol.stages.forEach(function(stage, index) {
protocol.stages.forEach((stage, index) => {
if (stage.id === id) {
stageIndex = index;
return false;
@ -36,7 +43,7 @@ function getStageIndex(protocol, id) {
});
return stageIndex;
}
};
Template.stageSortable.helpers({
/**
@ -44,7 +51,7 @@ Template.stageSortable.helpers({
*
* @returns {boolean} Whether or not the stage is currently being displayed
*/
isActiveStage: function() {
isActiveStage() {
// Rerun this function every time the layout manager has been updated
Session.get('LayoutManagerUpdated');
@ -53,7 +60,7 @@ Template.stageSortable.helpers({
return;
}
var currentStage = ProtocolEngine.getCurrentStageModel();
const currentStage = ProtocolEngine.getCurrentStageModel();
if (!currentStage) {
return false;
}
@ -66,8 +73,8 @@ Template.stageSortable.helpers({
*
* @returns {number|*}
*/
stageLabel: function() {
var stage = this;
stageLabel() {
const stage = this;
// If no Protocol Engine has been defined yet, stop here to prevent errors
if (!ProtocolEngine) {
@ -75,10 +82,10 @@ Template.stageSortable.helpers({
}
// Retrieve the last saved copy of the current protocol
var lastSavedCopy = HP.ProtocolStore.getProtocol(ProtocolEngine.protocol.id);
const lastSavedCopy = HangingProtocols.findOne(ProtocolEngine.protocol._id);
// Try to find the index of this stage in the previously saved copy
var stageIndex = getStageIndex(lastSavedCopy, stage.id);
let stageIndex = getStageIndex(lastSavedCopy, stage.id);
// If the stage is new, and therefore wasn't present in the last save,
// retrieve it's index in the array of new stage ids and use that for
@ -88,11 +95,11 @@ Template.stageSortable.helpers({
Session.get('timeAgoVariable');
// Find the index of the stage in the array of newly created stage IDs
var newStageNumber = ProtocolEngine.newStageIds.indexOf(stage.id) + 1;
const newStageNumber = ProtocolEngine.newStageIds.indexOf(stage.id) + 1;
// Use Moment.js to format the createdDate of this stage relative to the
// current time
var dateCreatedFromNow = moment(stage.createdDate).fromNow();
const dateCreatedFromNow = moment(stage.createdDate).fromNow();
// Return the label for the new stage,
// e.g. "New Stage 1 (created a few seconds ago)"
@ -108,7 +115,7 @@ Template.stageSortable.helpers({
*
* @returns {boolean} Whether or not a later stage exists
*/
isNextAvailable: function() {
isNextAvailable() {
// Run this helper whenever the ProtocolEngine / LayoutManager has changed
Session.get('LayoutManagerUpdated');
@ -125,7 +132,7 @@ Template.stageSortable.helpers({
*
* @returns {boolean} Whether or not an earlier stage exists
*/
isPreviousAvailable: function() {
isPreviousAvailable() {
// Run this helper whenever the ProtocolEngine / LayoutManager has changed
Session.get('LayoutManagerUpdated');
@ -143,9 +150,9 @@ Template.stageSortable.events({
/**
* Displays a stage when its title is clicked
*/
'click .sortable-item span': function() {
'click .sortable-item span'() {
// Retrieve the index of this stage in the display set sequences
var stageIndex = getStageIndex(ProtocolEngine.protocol, this.id);
const stageIndex = getStageIndex(ProtocolEngine.protocol, this.id);
// Display the selected stage
ProtocolEngine.setCurrentProtocolStage(stageIndex);
@ -154,12 +161,12 @@ Template.stageSortable.events({
* Creates a new stage and adds it to the currently loaded Protocol at
* the end of the display set sequence
*/
'click #addStage': function() {
'click #addStage'() {
// Retrieve the model describing the current stage
var stage = ProtocolEngine.getCurrentStageModel();
const stage = ProtocolEngine.getCurrentStageModel();
// Clone this stage to create a new stage
var newStage = stage.createClone();
const newStage = stage.createClone();
// Remove the stage's name if it has one
delete newStage.name;
@ -171,7 +178,7 @@ Template.stageSortable.events({
ProtocolEngine.newStageIds.push(newStage.id);
// Calculate the index of the last stage in the display set sequence
var stageIndex = ProtocolEngine.protocol.stages.length - 1;
const stageIndex = ProtocolEngine.protocol.stages.length - 1;
// Switch to the last stage in the display set sequence
ProtocolEngine.setCurrentProtocolStage(stageIndex);
@ -181,22 +188,22 @@ Template.stageSortable.events({
* the stages array. If it is the currently active stage, the current stage is
* set to one stage earlier in the display set sequence.
*/
'click .deleteStage': function() {
'click .deleteStage'() {
// If this is the only stage in the Protocol, stop here
if (ProtocolEngine.protocol.stages.length === 1) {
return;
}
var stageId = this.id;
const stageId = this.id;
var options = {
const options = {
title: 'Remove Protocol Stage',
text: 'Are you sure you would like to remove this Protocol Stage? This cannot be reversed.'
};
showConfirmDialog(function() {
OHIF.viewerbase.showConfirmDialog(() => {
// Retrieve the index of this stage in the display set sequences
var stageIndex = getStageIndex(ProtocolEngine.protocol, stageId);
const stageIndex = getStageIndex(ProtocolEngine.protocol, stageId);
// Remove it from the display set sequence
ProtocolEngine.protocol.stages.splice(stageIndex, 1);
@ -204,7 +211,7 @@ Template.stageSortable.events({
// If we have removed the currently active stage, switch to the one before it
if (ProtocolEngine.stage === stageIndex) {
// Make sure we don't try to switch to a stage index below zero
var newStageIndex = Math.max(stageIndex - 1, 0);
const newStageIndex = Math.max(stageIndex - 1, 0);
// Display the new stage
ProtocolEngine.setCurrentProtocolStage(newStageIndex);
@ -215,10 +222,10 @@ Template.stageSortable.events({
}, options);
},
'click .moveStageUp': function() {
'click .moveStageUp'() {
// Get the old and new indices following a 'sort' event
var oldIndex = ProtocolEngine.stage;
var newIndex = Math.max(ProtocolEngine.stage - 1, 0);
const oldIndex = ProtocolEngine.stage;
const newIndex = Math.max(ProtocolEngine.stage - 1, 0);
if (oldIndex === newIndex) {
return;
@ -235,10 +242,10 @@ Template.stageSortable.events({
// Update the Session variable to the UI re-renders
Session.set('LayoutManagerUpdated', Random.id());
},
'click .moveStageDown': function() {
'click .moveStageDown'() {
// Get the old and new indices following a 'sort' event
var oldIndex = ProtocolEngine.stage;
var newIndex = Math.min(ProtocolEngine.stage + 1, ProtocolEngine.protocol.stages.length - 1);
const oldIndex = ProtocolEngine.stage;
const newIndex = Math.min(ProtocolEngine.stage + 1, ProtocolEngine.protocol.stages.length - 1);
if (oldIndex === newIndex) {
return;

View File

@ -1,4 +1,10 @@
var keys = {
import { Blaze } from 'meteor/blaze';
import { $ } from 'meteor/jquery';
import { Template } from 'meteor/templating';
import { Viewerbase } from 'meteor/ohif:viewerbase';
const keys = {
ESC: 27,
ENTER: 13
};
@ -9,7 +15,7 @@ var keys = {
*
* @param dialog The DOM element of the dialog to close
*/
function closeHandler(dialog) {
const closeHandler = dialog => {
// Hide the lesion dialog
$(dialog).css('display', 'none');
@ -17,8 +23,8 @@ function closeHandler(dialog) {
$('.removableBackdrop').remove();
// Restore the focus to the active viewport
setFocusToActiveViewport();
}
Viewerbase.setFocusToActiveViewport();
};
/**
* Displays and updates the UI of the Text Entry Dialog given a new title,
@ -30,10 +36,10 @@ function closeHandler(dialog) {
*/
openTextEntryDialog = function(title, instructions, currentValue, doneCallback) {
// Get the lesion location dialog
var dialog = $('.textEntryDialog');
const dialog = $('.textEntryDialog');
// Clear any input that is still on the page
var currentValueInput = dialog.find('input.currentValue');
const currentValueInput = dialog.find('input.currentValue');
currentValueInput.val(currentValue);
// Store the Dialog DOM data, rule level and rule in the template data
@ -49,10 +55,10 @@ openTextEntryDialog = function(title, instructions, currentValue, doneCallback)
dialog.css('display', 'block');
// Show the backdrop
UI.render(Template.removableBackdrop, document.body);
Blaze.render(Template.removableBackdrop, document.body);
// Make sure the context menu is closed when the user clicks away
$('.removableBackdrop').one('mousedown touchstart', function() {
$('.removableBackdrop').one('mousedown touchstart', () => {
closeHandler(dialog);
});
};
@ -68,17 +74,17 @@ Template.textEntryDialog.events({
* Save the user-specified text
*
*/
'click .save': function() {
'click .save'() {
// Retrieve the input properties to the template
var dialog = Template.textEntryDialog.dialog;
var currentValue = dialog.find('input.currentValue').val();
const dialog = Template.textEntryDialog.dialog;
const currentValue = dialog.find('input.currentValue').val();
// If currentValue input is undefined, prevent saving this rule
if (currentValue === undefined) {
return;
}
var doneCallback = Template.textEntryDialog.doneCallback;
const doneCallback = Template.textEntryDialog.doneCallback;
if (doneCallback) {
doneCallback(currentValue);
}
@ -89,7 +95,7 @@ Template.textEntryDialog.events({
/**
* Allow the user to click the Cancel button to close the dialog
*/
'click .cancel': function() {
'click .cancel'() {
closeHandler(Template.textEntryDialog.dialog);
},
/**
@ -98,22 +104,22 @@ Template.textEntryDialog.events({
* @param event The Keydown event details
* @returns {boolean} Return false to prevent bubbling of the event
*/
'keydown .textEntryDialog': function(event) {
var dialog = Template.textEntryDialog.dialog;
'keydown .textEntryDialog'(event) {
const dialog = Template.textEntryDialog.dialog;
// If Esc key is pressed, close the dialog
if (event.which === keys.ESC) {
closeHandler(dialog);
return false;
} else if (event.which === keys.ENTER) {
var currentValue = dialog.find('input.currentValue').val();
const currentValue = dialog.find('input.currentValue').val();
// If currentValue input is undefined, prevent saving this rule
if (currentValue === undefined) {
return;
}
var doneCallback = Template.textEntryDialog.doneCallback;
const doneCallback = Template.textEntryDialog.doneCallback;
if (doneCallback) {
doneCallback(currentValue);
}
@ -128,9 +134,9 @@ Template.textEntryDialog.events({
* @param event The Change event for the input
* @param template The current template context
*/
'change input.currentValue': function(event, template) {
'change input.currentValue'(event, template) {
// Get the DOM element representing the input box
var input = $(event.currentTarget);
const input = $(event.currentTarget);
// Update the template data with the current value
Template.textEntryDialog.currentValue = input.val();

View File

@ -189,8 +189,8 @@ var dialogPolyfill = (function() {
dialogPolyfill.registerDialog = function(element) {
if (element.show) {
console.warn("This browser already supports <dialog>, the polyfill " +
"may not work correctly.");
// console.warn("This browser already supports <dialog>, the polyfill " +
// "may not work correctly.");
}
element.show = dialogPolyfill.showDialog.bind(element, false);
element.showModal = dialogPolyfill.showDialog.bind(element, true);

View File

@ -1,6 +1,10 @@
import { Template } from 'meteor/templating';
import { Tracker } from 'meteor/tracker';
import { Session } from 'meteor/session';
// OHIF Modules
import { OHIF } from 'meteor/ohif:core';
// Local Modules
import { getImageId } from '../../../lib/getImageId.js';
Template.imageThumbnail.onCreated(() => {
const instance = Template.instance();
@ -26,6 +30,10 @@ Template.imageThumbnail.onRendered(() => {
const element = $element.get(0);
instance.refreshImage = () => {
if (!element) {
return;
}
// Disable cornerstone for thumbnail element and remove its canvas
cornerstone.disable(element);
@ -75,7 +83,9 @@ Template.imageThumbnail.onDestroyed(() => {
const $element = $parent.find('.imageThumbnailCanvas');
const element = $element.get(0);
cornerstone.disable(element);
if (element) {
cornerstone.disable(element);
}
});
Template.imageThumbnail.helpers({

View File

@ -1,202 +1,9 @@
const cloneElement = (element, targetId) => {
// Clone the DOM element
const clone = element.cloneNode(true);
import { Template } from 'meteor/templating';
import { _ } from 'meteor/underscore';
import { Session } from 'meteor/session';
// Find any canvas children to clone
const clonedCanvases = $(clone).find('canvas');
clonedCanvases.each((canvasIndex, clonedCanvas) => {
// Draw from the original canvas to the cloned canvas
const context = clonedCanvas.getContext('2d');
const thumbnailCanvas = $(element).find('canvas').get(canvasIndex);
context.drawImage(thumbnailCanvas, 0, 0);
});
// Update the clone with the targetId
clone.id = targetId;
clone.style.visibility = 'hidden';
return clone;
};
const thumbnailDragStartHandler = (event, data) => {
// Prevent any scrolling behaviour normally caused by the original event
event.originalEvent.preventDefault();
// Identify the current study and series index from the thumbnail's DOM position
const targetThumbnail = event.currentTarget;
const $imageThumbnail = $(targetThumbnail);
// Clone the image thumbnail
const targetId = 'DragClone';
const clone = cloneElement(targetThumbnail, targetId);
const $clone = $(clone);
$clone.addClass('imageThumbnailClone');
// Set pointerEvents to pass through the clone DOM element
// This is necessary in order to identify what is below it
// when using document.elementFromPoint
clone.style.pointerEvents = 'none';
// Append the clone to the body
document.body.appendChild(clone);
// Set the cursor x and y positions from the current touch/mouse coordinates
let cursorX, cursorY;
// Handle touchStart cases
if (event.type === 'touchstart') {
cursorX = event.originalEvent.touches[0].pageX;
cursorY = event.originalEvent.touches[0].pageY;
} else {
cursorX = event.pageX;
cursorY = event.pageY;
// Also hook up event handlers for mouse events
const handlers = {};
handlers.mousemove = event => thumbnailDragHandler(event);
handlers.mouseup = event => thumbnailDragEndHandler(event, data, handlers);
$(document).on('mousemove', handlers.mousemove);
$(document).on('mouseup', handlers.mouseup);
}
// This block gets the current offset of the touch/mouse
// relative to the window
//
// i.e. Where did the user grab it from?
const offset = $imageThumbnail.offset();
const left = offset.left;
const top = offset.top;
// This difference is saved for later so the element movement looks normal
const diff = {
x: cursorX - left,
y: cursorY - top
};
$clone.data('diff', diff);
// This sets the default style properties of the cloned element so it is
// ready to be dragged around the page
$clone.css({
left: cursorX - diff.x,
position: 'fixed',
top: cursorY - diff.y,
visibility: 'hidden',
'z-index': 100000
});
};
const thumbnailDragHandler = event => {
// Get the touch/mouse coordinates from the event
let cursorX, cursorY;
if (event.type === 'touchmove') {
cursorX = event.originalEvent.changedTouches[0].pageX;
cursorY = event.originalEvent.changedTouches[0].pageY;
} else {
cursorX = event.pageX;
cursorY = event.pageY;
}
// Find the clone element and update it's position on the page
const $clone = $('#DragClone');
const diff = $clone.data('diff');
$clone.css({
left: cursorX - diff.x,
position: 'fixed',
top: cursorY - diff.y,
visibility: 'visible',
'z-index': 100000
});
// Identify the element below the current cursor position
const elemBelow = document.elementFromPoint(cursorX, cursorY);
// If none exists, stop here
if (!elemBelow) {
return;
}
// Remove any current faded effects on viewports
$('.imageViewerViewport canvas').removeClass('faded');
// Figure out what to do depending on what we're dragging over
const $viewportsDraggedOver = $(elemBelow).parents('.imageViewerViewport');
if ($viewportsDraggedOver.length) {
// If we're dragging over a non-empty viewport, fade it and change the cursor style
$viewportsDraggedOver.find('canvas').not('.magnifyTool').addClass('faded');
document.body.style.cursor = 'copy';
} else if (elemBelow.classList.contains('imageViewerViewport') && elemBelow.classList.contains('empty')) {
// If we're dragging over an empty viewport, just change the cursor style
document.body.style.cursor = 'copy';
} else {
// Otherwise, keep the cursor as no-drop style
document.body.style.cursor = 'no-drop';
}
};
const thumbnailDragEndHandler = (event, data, handlers) => {
// Remove the mouse event listeners
if (handlers) {
$(document).off('mousemove', handlers.mousemove);
$(document).off('mouseup', handlers.mouseup);
}
// Reset the cursor style to the default
document.body.style.cursor = 'auto';
// Get the cloned element
const $clone = $('#DragClone');
// If it doesn't exist, stop here
if (!$clone.length) {
return;
}
const top = $clone.offset().top;
const left = $clone.offset().left;
const diff = $clone.data('diff');
// Identify the element below the cloned element position
const elemBelow = document.elementFromPoint(left + diff.x, top + diff.y);
// Remove all cloned elements from the page
$('.imageThumbnailClone').remove();
// Remove any current faded effects on viewports
$('.imageViewerViewport canvas').removeClass('faded');
// If none exists, stop here
if (!elemBelow) {
return;
}
// Remove any fade effects on the element below
elemBelow.classList.remove('faded');
let element;
const $viewportsDraggedOver = $(elemBelow).closest('.imageViewerViewport');
if ($viewportsDraggedOver.length) {
// If we're dragging over a non-empty viewport, retrieve it
element = $viewportsDraggedOver.get(0);
} else if (elemBelow.classList.contains('imageViewerViewport') &&
elemBelow.classList.contains('empty')) {
// If we're dragging over an empty viewport, retrieve that instead
element = elemBelow;
} else {
// Otherwise, stop here
return false;
}
// If there is no stored drag and drop data, stop here
if (!data) {
return false;
}
// Get the dropped viewport index
const viewportIndex = $('.imageViewerViewport').index(element);
// Rerender the viewport using the dragged thumbnail data
window.layoutManager.rerenderViewportWithNewDisplaySet(viewportIndex, data);
return false;
};
import { OHIF } from 'meteor/ohif:core';
import { thumbnailDragHandlers } from '../../../lib/thumbnailDragHandlers';
Template.thumbnailEntry.onCreated(() => {
const instance = Template.instance();
@ -209,16 +16,16 @@ Template.thumbnailEntry.events({
// Event handlers for drag and drop
'touchstart .thumbnailEntry, mousedown .thumbnailEntry'(event, instance) {
const data = instance.data.thumbnail.stack;
instance.isDragAndDrop && thumbnailDragStartHandler(event, data);
instance.isDragAndDrop && thumbnailDragHandlers.thumbnailDragStartHandler(event, data);
},
'touchmove .thumbnailEntry'(event, instance) {
instance.isDragAndDrop && thumbnailDragHandler(event);
instance.isDragAndDrop && thumbnailDragHandlers.thumbnailDragHandler(event);
},
'touchend .thumbnailEntry'(event, instance) {
const data = instance.data.thumbnail.stack;
instance.isDragAndDrop && thumbnailDragEndHandler(event, data);
instance.isDragAndDrop && thumbnailDragHandlers.thumbnailDragEndHandler(event, data);
},
// Event handlers for click (quick switch)
@ -228,7 +35,7 @@ Template.thumbnailEntry.events({
const data = instance.data.thumbnail.stack;
// Rerender the viewport using the clicked thumbnail data
window.layoutManager.rerenderViewportWithNewDisplaySet(instance.data.viewportIndex, data);
OHIF.viewerbase.layoutManager.rerenderViewportWithNewDisplaySet(instance.data.viewportIndex, data);
}
},
@ -241,7 +48,7 @@ Template.thumbnailEntry.events({
const data = instance.data.thumbnail.stack;
// Rerender the viewport using the clicked thumbnail data
window.layoutManager.rerenderViewportWithNewDisplaySet(viewportIndex, data);
OHIF.viewerbase.layoutManager.rerenderViewportWithNewDisplaySet(viewportIndex, data);
}
});

View File

@ -1,118 +1,3 @@
// ------ Tool configuration functions ----- //
getAnnotationTextCallback = function(doneChangingTextCallback) {
// This handles the text entry for the annotation tool
function keyPressHandler(e) {
// If Enter or Esc are pressed, close the dialog
if (e.which === 13 || e.which === 27) {
closeHandler();
}
}
function closeHandler() {
dialog.get(0).close();
doneChangingTextCallback(getTextInput.val());
// Reset the text value
getTextInput.val('');
// Reset the focus to the active viewport element
// This makes the mobile Safari keyboard close
const element = getActiveViewportElement();
$(element).focus();
}
const dialog = $('#annotationDialog');
if (dialog.get(0).open === true) {
return;
}
const getTextInput = $('.annotationTextInput');
// Focus on the text input to open the Safari keyboard
getTextInput.focus();
dialog.get(0).showModal();
const confirm = dialog.find('.annotationDialogConfirm');
confirm.off('click');
confirm.on('click', () => {
closeHandler();
});
// Use keydown since keypress doesn't handle ESC in Chrome
dialog.off('keydown');
dialog.on('keydown', keyPressHandler);
};
changeAnnotationTextCallback = function(data, eventData, doneChangingTextCallback) {
const dialog = $('#relabelAnnotationDialog');
if (dialog.get(0).open === true) {
return;
}
if (isTouchDevice()) {
// Center the dialog on screen on touch devices
dialog.css({
top: 0,
left: 0,
right: 0,
bottom: 0,
margin: 'auto'
});
} else {
// Place the dialog above the tool that is being relabelled
dialog.css({
top: eventData.currentPoints.page.y - dialog.outerHeight() - 20,
left: eventData.currentPoints.page.x - dialog.outerWidth() / 2
});
}
const getTextInput = dialog.find('.annotationTextInput');
const confirm = dialog.find('.relabelConfirm');
const remove = dialog.find('.relabelRemove');
getTextInput.val(data.text);
// Focus on the text input to open the Safari keyboard
getTextInput.focus();
dialog.get(0).showModal();
confirm.off('click');
confirm.on('click', function() {
dialog.get(0).close();
doneChangingTextCallback(data, getTextInput.val());
});
// If the remove button is clicked, delete this marker
remove.off('click');
remove.on('click', function() {
dialog.get(0).close();
doneChangingTextCallback(data, undefined, true);
});
dialog.off('keydown');
dialog.on('keydown', keyPressHandler);
function keyPressHandler(e) {
// If Enter is pressed, close the dialog
if (e.which === 13) {
closeHandler();
}
}
function closeHandler() {
dialog.get(0).close();
doneChangingTextCallback(data, getTextInput.val());
// Reset the text value
getTextInput.val('');
// Reset the focus to the active viewport element
// This makes the mobile Safari keyboard close
const element = getActiveViewportElement();
$(element).focus();
}
};
Template.annotationDialogs.onRendered(() => {
const instance = Template.instance();
const dialogIds = ['annotationDialog', 'relabelAnnotationDialog'];

View File

@ -1,9 +1,12 @@
import { Template } from 'meteor/templating';
import { SimpleSchema } from 'meteor/aldeed:simple-schema';
import { OHIF } from 'meteor/ohif:core';
import { Session } from 'meteor/session';
import { _ } from 'meteor/underscore';
import { $ } from 'meteor/jquery';
import { OHIF } from 'meteor/ohif:core';
import { viewportUtils } from '../../../lib/viewportUtils';
import { switchToImageRelative } from '../../../lib/switchToImageRelative';
import { switchToImageByIndex } from '../../../lib/switchToImageByIndex';
Template.cineDialog.onCreated(() => {
const instance = Template.instance();
@ -34,18 +37,15 @@ Template.cineDialog.onCreated(() => {
OHIF.viewer.cine.framesPerSecond = rate;
// Update playClip toolData for this imageId
const element = getActiveViewportElement();
const element = viewportUtils.getActiveViewportElement();
if (!element) {
return;
}
const playClipToolData = cornerstoneTools.getToolState(element, 'playClip');
playClipToolData.data[0].framesPerSecond = OHIF.viewer.cine.framesPerSecond;
// If the movie is playing, stop/start to update the framerate
if (isPlaying()) {
if (viewportUtils.isPlaying()) {
cornerstoneTools.stopClip(element);
cornerstoneTools.playClip(element);
cornerstoneTools.playClip(element, OHIF.viewer.cine.framesPerSecond);
}
Session.set('UpdateCINE', Random.id());
@ -55,7 +55,7 @@ Template.cineDialog.onCreated(() => {
instance.api = {
displaySetPrevious: () => OHIF.viewer.moveDisplaySets(false),
displaySetNext: () => OHIF.viewer.moveDisplaySets(true),
cineToggle: () => toggleCinePlay(),
cineToggle: () => viewportUtils.toggleCinePlay(),
cineFirst: () => switchToImageByIndex(0),
cineLast: () => switchToImageByIndex(-1),
cinePrevious: () => switchToImageRelative(-1),
@ -80,13 +80,19 @@ Template.cineDialog.onCreated(() => {
Tracker.afterFlush(() => {
// Get the active viewportElement
const element = getActiveViewportElement();
const element = viewportUtils.getActiveViewportElement();
if (!element) {
return;
}
// check if playClip tool has been initialized...
const playClipData = cornerstoneTools.getToolState(element, 'playClip');
if (!playClipData) {
return;
}
// Get the cornerstone playClip tool data
const toolData = cornerstoneTools.getToolState(element, 'playClip').data[0];
const toolData = playClipData.data[0];
// Get the cine object
const cine = OHIF.viewer.cine;
@ -99,7 +105,10 @@ Template.cineDialog.onCreated(() => {
cine.loop = _.isUndefined(cine.loop) ? true : cine.loop;
// Set the updated data on the form inputs
instance.$('form:first').data('component').value(cine);
const elementComponent = instance.$('form:first').data('component');
if (elementComponent) {
elementComponent.value(cine);
}
// Update the session to refresh the framerate text
Session.set('UpdateCINE', Random.id());
@ -204,11 +213,9 @@ Template.cineDialog.onCreated(() => {
});
Template.cineDialog.onRendered(() => {
const instance = Template.instance();
let dialog = instance.$('#cineDialog'),
singleRowLayout = OHIF.uiSettings.displayEchoUltrasoundWorkflow;
const dialog = instance.$('#cineDialog');
const singleRowLayout = OHIF.uiSettings.displayEchoUltrasoundWorkflow;
// set dialog in optimal position and make sure it continues in a optimal position...
// ... when the window has been resized
@ -222,7 +229,10 @@ Template.cineDialog.onRendered(() => {
instance.setResizeHandler(instance.setOptimalPosition);
// Make the CINE dialog bounded and draggable
dialog.bounded().draggable({ defaultElementCursor: 'move' });
dialog.draggable({ defaultElementCursor: 'move' });
// Polyfill for older browsers
dialogPolyfill.registerDialog(dialog.get(0));
// Prevent dialog from being dragged when user clicks any button
dialog.find('.cine-navigation, .cine-controls, .cine-options').on('mousedown touchstart', function (e) {
@ -239,7 +249,7 @@ Template.cineDialog.onDestroyed(() => {
Template.cineDialog.events({
'change [data-key=loop] input'(event, instance) {
const element = getActiveViewportElement();
const element = viewportUtils.getActiveViewportElement();
const playClipToolData = cornerstoneTools.getToolState(element, 'playClip');
playClipToolData.data[0].loop = $(event.currentTarget).is(':checked');
OHIF.viewer.cine.loop = playClipToolData.data[0].loop;
@ -254,7 +264,7 @@ Template.cineDialog.events({
Template.cineDialog.helpers({
isPlaying() {
return isPlaying();
return viewportUtils.isPlaying();
},
framerate() {
@ -264,11 +274,11 @@ Template.cineDialog.helpers({
displaySetDisabled(isNext) {
Session.get('LayoutManagerUpdated');
return !OHIF.viewer.canMoveDisplaySets(isNext) ? 'disabled' : '';
return !OHIF.viewerbase.layoutManager.canMoveDisplaySets(isNext) ? 'disabled' : '';
},
buttonDisabled() {
return hasMultipleFrames();
return viewportUtils.hasMultipleFrames();
},
getClassNames(baseCls) {

View File

@ -1,69 +1,26 @@
function closeHandler() {
// Hide the lesion dialog
$('#confirmDeleteDialog').css('display', 'none');
import { Template } from 'meteor/templating';
// 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;
}
};
import { dialogUtils } from '../../../lib/dialogUtils';
// Global object of key names (TODO: put this somewhere else)
keys = {
const keys = {
ESC: 27,
ENTER: 13
};
Template.confirmDeleteDialog.events({
'click #cancel, click #close': function() {
closeHandler();
'click #cancel, click #close'() {
// Action canceled, just close dialog without calling callback
dialogUtils.closeHandler(false);
},
'click #confirm': function() {
var doneCallback = Template.confirmDeleteDialog.doneCallback;
if (doneCallback && typeof doneCallback === 'function') {
doneCallback();
}
closeHandler();
'click #confirm'() {
// Action confirmed, close dialog and calls callback, if exists
dialogUtils.closeHandler();
},
'keydown #confirmDeleteDialog': function(e) {
'keydown #confirmDeleteDialog'(e) {
// Action canceled, just close dialog without calling callback
if (e.which === keys.ESC) {
closeHandler();
dialogUtils.closeHandler(false);
return false;
}
@ -71,15 +28,10 @@ Template.confirmDeleteDialog.events({
return;
}
var doneCallback = Template.confirmDeleteDialog.doneCallback;
// If Enter is pressed, close the dialog
// If Enter is pressed
if (e.which === keys.ENTER) {
if (doneCallback && typeof doneCallback === 'function') {
doneCallback();
}
closeHandler();
// Action confirmed, close dialog and calls callback, if exists
dialogUtils.closeHandler();
}
}
});

View File

@ -15,13 +15,13 @@ Template.displaySetNavigation.events({
return;
}
OHIF.viewer.moveDisplaySets(false);
OHIF.viewerbase.layoutManager.moveDisplaySets(false);
}
});
Template.displaySetNavigation.helpers({
disableButton(isNext) {
Session.get('LayoutManagerUpdated');
return !OHIF.viewer.canMoveDisplaySets(isNext);
return !OHIF.viewerbase.layoutManager.canMoveDisplaySets(isNext);
}
});

View File

@ -1,3 +1,8 @@
import { Template } from 'meteor/templating';
import { $ } from 'meteor/jquery';
import { setActiveViewport } from '../../../lib/setActiveViewport';
import { switchToImageByIndex } from '../../../lib/switchToImageByIndex';
const slideTimeoutTime = 40;
let slideTimeout;
@ -33,7 +38,7 @@ Template.imageControls.events({
OHIF.viewer.hotkeyFunctions.scrollUp();
}
},
'input #imageSlider, change #imageSlider'(event) {
'input input[type=range], change input[type=range]'(event) {
// Note that we throttle requests to prevent the
// user's ultrafast scrolling from firing requests too quickly.
clearTimeout(slideTimeout);
@ -41,7 +46,7 @@ Template.imageControls.events({
// Using the slider in an inactive viewport
// should cause that viewport to become active
const slider = $(event.currentTarget);
const newActiveElement = slider.parents().eq(2).siblings('.imageViewerViewport').get(0);
const newActiveElement = slider.parents('.viewportContainer').find('.imageViewerViewport');
setActiveViewport(newActiveElement);
// Subtract 1 here since the slider goes from 1 to N images

View File

@ -1,3 +1,7 @@
import { Template } from 'meteor/templating';
import { $ } from 'meteor/jquery';
import { viewportUtils } from '../../../lib/viewportUtils';
Template.layoutButton.events({
// TODO: Check why 'click' event won't fire?
'mousedown .js-dropdown-toggle'(event) {
@ -12,6 +16,6 @@ Template.layoutButton.events({
});
// Open or close the layout chooser dialog
toggleDialog($dropdown);
viewportUtils.toggleDialog($dropdown);
}
});

View File

@ -1,3 +1,9 @@
import { Template } from 'meteor/templating';
import { Session } from 'meteor/session';
import { OHIF } from 'meteor/ohif:core';
import 'meteor/ohif:viewerbase';
import { viewportUtils } from '../../../lib/viewportUtils';
Template.layoutChooser.onRendered(() => {
const instance = Template.instance();
@ -35,12 +41,12 @@ Template.layoutChooser.onRendered(() => {
// Refresh layout chooser highlighting based on current viewports state
instance.refreshHighlights = () => {
// Stop here if layoutManager is not defined yet
if (!window.layoutManager) {
if (!OHIF.viewerbase.layoutManager) {
return;
}
// Get the layout rows and columns amount
const info = window.layoutManager.layoutProps;
const info = OHIF.viewerbase.layoutManager.layoutProps;
// get the limiter cell
const cell = instance.$('tr').eq(info.rows - 1).children().eq(info.columns - 1);
@ -77,11 +83,11 @@ Template.layoutChooser.events({
columns: columnIndex + 1
};
window.layoutManager.layoutTemplateName = 'gridLayout';
window.layoutManager.layoutProps = layoutProps;
window.layoutManager.updateViewports();
OHIF.viewerbase.layoutManager.layoutTemplateName = 'gridLayout';
OHIF.viewerbase.layoutManager.layoutProps = layoutProps;
OHIF.viewerbase.layoutManager.updateViewports();
const $dropdown = instance.$('.layoutChooser');
toggleDialog($dropdown);
viewportUtils.toggleDialog($dropdown);
}
});

View File

@ -1,3 +1,6 @@
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import { $ } from 'meteor/jquery';
import { OHIF } from 'meteor/ohif:core';
Meteor.startup(function() {
@ -8,43 +11,41 @@ Meteor.startup(function() {
var loadHandlerTimeout;
startLoadingHandler = function(element) {
const startLoadingHandler = function(element) {
clearTimeout(loadHandlerTimeout);
loadHandlerTimeout = setTimeout(function() {
console.log('startLoading');
var elem = $(element);
elem.siblings('.imageViewerErrorLoadingIndicator').css('display', 'none');
elem.find('canvas').not('.magnifyTool').addClass("faded");
elem.find('canvas').not('.magnifyTool').addClass('faded');
elem.siblings('.imageViewerLoadingIndicator').css('display', 'block');
}, OHIF.viewer.loadIndicatorDelay);
};
doneLoadingHandler = function(element) {
const doneLoadingHandler = function(element) {
clearTimeout(loadHandlerTimeout);
var elem = $(element);
elem.siblings('.imageViewerErrorLoadingIndicator').css('display', 'none');
elem.find('canvas').not('.magnifyTool').removeClass("faded");
elem.find('canvas').not('.magnifyTool').removeClass('faded');
elem.siblings('.imageViewerLoadingIndicator').css('display', 'none');
};
errorLoadingHandler = function(element, imageId, error, source) {
const errorLoadingHandler = function(element, imageId, error, source) {
clearTimeout(loadHandlerTimeout);
var elem = $(element);
// Could probably chain all of these, but this is more readable
elem.find('canvas').not('.magnifyTool').removeClass("faded");
elem.find('canvas').not('.magnifyTool').removeClass('faded');
elem.siblings('.imageViewerLoadingIndicator').css('display', 'none');
// Don't display errors from the stackPrefetch tool
if (source === "stackPrefetch") {
if (source === 'stackPrefetch') {
return;
}
var errorLoadingIndicator = elem.siblings('.imageViewerErrorLoadingIndicator');
errorLoadingIndicator.css('display', 'block');
var cleanedImageId = imageId;
// This is just used to expand upon some error messages that are sent
// when things fail. An example is a network error throwing the error
// which is only described as "network".
@ -57,7 +58,7 @@ errorLoadingHandler = function(element, imageId, error, source) {
error = errorDetails[error];
}
errorLoadingIndicator.find('.description').text("An error has occurred while loading image: " + cleanedImageId);
errorLoadingIndicator.find('.description').text("An error has occurred while loading image: " + imageId);
if (error) {
errorLoadingIndicator.find('.details').text("Details: " + error);
}

View File

@ -1,8 +1,11 @@
import { Template } from 'meteor/templating';
import { viewportUtils } from '../../../lib/viewportUtils';
Template.playClipButton.helpers({
isPlaying: function() {
return isPlaying();
return viewportUtils.isPlaying();
},
disableButton() {
return hasMultipleFrames();
return viewportUtils.hasMultipleFrames();
}
});

View File

@ -1,6 +1,10 @@
import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
import { Session } from 'meteor/session';
import { $ } from 'meteor/jquery';
import { OHIF } from 'meteor/ohif:core';
import 'meteor/ohif:viewerbase';
Template.studySeriesQuickSwitch.onCreated(() => {
const instance = Template.instance();
@ -10,7 +14,7 @@ Template.studySeriesQuickSwitch.onCreated(() => {
// Gets the viewport data for the given viewport index
instance.getViewportData = viewportIndex => {
const layoutManager = window.layoutManager;
const layoutManager = OHIF.viewerbase.layoutManager;
return layoutManager && layoutManager.viewportData && layoutManager.viewportData[viewportIndex];
};

View File

@ -3,6 +3,8 @@ import { ReactiveVar } from 'meteor/reactive-var';
import { Tracker } from 'meteor/tracker';
import { _ } from 'meteor/underscore';
import { OHIFError } from '../../../lib/classes/OHIFError';
Template.studyTimepointBrowser.onCreated(() => {
const instance = Template.instance();
@ -36,7 +38,7 @@ Template.studyTimepointBrowser.onCreated(() => {
const notYetLoaded = StudyListStudies.findOne(query);
if (!notYetLoaded) {
throw 'No study data available for Study: ' + studyInstanceUid;
throw new OHIFError(`No study data available for Study: ${studyInstanceUid}`);
}
return notYetLoaded;

View File

@ -3,6 +3,7 @@ import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
import { _ } from 'meteor/underscore';
import { $ } from 'meteor/jquery';
import { sortingManager } from '../../../lib/sortingManager';
Template.studyTimepointStudy.onCreated(() => {
const instance = Template.instance();
@ -16,11 +17,6 @@ Template.studyTimepointStudy.onCreated(() => {
return isGlobal ? $(selector) : instance.$browser.find(selector);
};
// Set all the studies with the same uid to loading state
instance.setLoading = () => {
};
// Set the current study as selected in the studies list
instance.select = (isQuickSwitch=false) => {
const studyInstanceUid = instance.data.study.studyInstanceUid;
@ -110,7 +106,7 @@ Template.studyTimepointStudy.events({
const $studies = instance.getStudyElement(true);
$studies.trigger('loadStarted');
getStudyMetadata(studyInstanceUid, study => {
study.displaySets = createStacks(study);
study.displaySets = sortingManager.getDisplaySets(study);
instance.data.study = study;
ViewerStudies.insert(study, () => {
// To make sure studies are rendered in the DOM

View File

@ -2,6 +2,7 @@ import { OHIF } from 'meteor/ohif:core';
import { Template } from 'meteor/templating';
import { Session } from 'meteor/session';
import { _ } from 'meteor/underscore';
import { toolManager } from '../../../lib/toolManager';
Template.toolbarSectionButton.onCreated(() => {
const instance = Template.instance();

View File

@ -1,5 +1,20 @@
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import { Session } from 'meteor/session';
import { OHIF } from 'meteor/ohif:core';
import { unloadHandlers } from '../../../lib/unloadHandlers.js';
// Local Modules
import { toolManager } from '../../../lib/toolManager';
import { unloadHandlers } from '../../../lib/unloadHandlers';
import { hotkeyUtils } from '../../../lib/hotkeyUtils';
import { ResizeViewportManager } from '../../../lib/classes/ResizeViewportManager';
import { LayoutManager } from '../../../lib/classes/LayoutManager';
Meteor.startup(() => {
// Create the synchronizer used to update reference lines
OHIF.viewer.updateImageSynchronizer = new cornerstoneTools.Synchronizer('CornerstoneNewImage', cornerstoneTools.updateImageSynchronizer);
window.ResizeViewportManager = window.ResizeViewportManager || new ResizeViewportManager();
});
Template.viewerMain.onCreated(() => {
// Attach the Window resize listener
@ -8,23 +23,22 @@ Template.viewerMain.onCreated(() => {
// and change it to jQuery._data(window, 'events')['resize'].
// Otherwise this function will be probably overrided.
// See cineDialog instance.setResizeHandler function
window.addEventListener('resize', handleResize);
window.addEventListener('resize', window.ResizeViewportManager.handleResize.bind(window.ResizeViewportManager));
// Add beforeUnload event handler to check for unsaved changes
window.addEventListener('beforeunload', unloadHandlers.beforeUnload);
// Create the synchronizer used to update reference lines
OHIF.viewer.updateImageSynchronizer = new cornerstoneTools.Synchronizer('CornerstoneNewImage', cornerstoneTools.updateImageSynchronizer);
});
Template.viewerMain.onRendered(() => {
const instance = Template.instance();
HP.ProtocolStore.onReady(() => {
instance.subscribe('hangingprotocols', () => {
const { studies, currentTimepointId, measurementApi, timepointIds } = instance.data;
const parentElement = instance.$('#layoutManagerTarget').get(0);
window.layoutManager = new LayoutManager(parentElement, studies);
OHIF.viewerbase.layoutManager = new LayoutManager(parentElement, studies);
OHIF.viewerbase.layoutManager.updateViewports();
// Default actions for Associated Studies
if(currentTimepointId) {
// Follow-up studies: same as the first measurement in the table
@ -73,18 +87,22 @@ Template.viewerMain.onRendered(() => {
}
// Toggle Measurement Table
instance.data.state.set('rightSidebar', 'measurements');
if(instance.data.state) {
instance.data.state.set('rightSidebar', 'measurements');
}
}
// Hide as default for single study
else {
instance.data.state.set('rightSidebar', null);
if(instance.data.state) {
instance.data.state.set('rightSidebar', null);
}
}
ProtocolEngine = new HP.ProtocolEngine(window.layoutManager, studies);
HP.setEngine(ProtocolEngine);
// ProtocolEngine = new HP.ProtocolEngine(OHIF.viewerbase.layoutManager, studies);
// HP.setEngine(ProtocolEngine);
// Enable hotkeys
enableHotkeys();
hotkeyUtils.enableHotkeys();
Session.set('ViewerMainReady', Random.id());
});
@ -94,7 +112,7 @@ Template.viewerMain.onDestroyed(() => {
OHIF.log.info('viewerMain onDestroyed');
// Remove the Window resize listener
window.removeEventListener('resize', handleResize);
window.removeEventListener('resize', window.ResizeViewportManager.handleResize.bind(window.ResizeViewportManager));
// Remove beforeUnload event handler...
window.removeEventListener('beforeunload', unloadHandlers.beforeUnload);
@ -102,6 +120,6 @@ Template.viewerMain.onDestroyed(() => {
// Destroy the synchronizer used to update reference lines
OHIF.viewer.updateImageSynchronizer.destroy();
delete window.layoutManager;
delete OHIF.viewerbase.layoutManager;
delete ProtocolEngine;
});

View File

@ -1,253 +1,147 @@
function getElementIfNotEmpty(viewportIndex) {
// Meteor template helpers run more often than expected
// They often seem to run just before the whole template is rendered
// This meant that the onRendered event hadn't fired yet, so the
// element wasn't enabled / set empty yet. The check here
// for canvases under the 'enabled' element div is to prevent
// 'undefined' errors from the helper functions
var imageViewerViewports = $('.imageViewerViewport'),
element = imageViewerViewports.get(viewportIndex),
canvases = imageViewerViewports.eq(viewportIndex).find('canvas');
if (!element || $(element).hasClass('empty') || canvases.length === 0) {
return;
}
// Check to make sure the element is enabled.
try {
var enabledElement = cornerstone.getEnabledElement(element);
} catch(error) {
return;
}
return element;
}
function getStackDataIfNotEmpty(viewportIndex) {
const element = getElementIfNotEmpty(viewportIndex);
if (!element) {
return;
}
const stackToolData = cornerstoneTools.getToolState(element, 'stack');
if (!stackToolData ||
!stackToolData.data ||
!stackToolData.data.length) {
return;
}
const stack = stackToolData.data[0];
if (!stack) {
return;
}
return stack;
}
function getPatient(property) {
Session.get('CornerstoneNewImage' + this.viewportIndex);
if (!this.imageId) {
return false;
}
var patient = cornerstoneTools.metaData.get('patient', this.imageId);
if (!patient) {
return '';
}
return patient[property];
}
function getStudy(property) {
Session.get('CornerstoneNewImage' + this.viewportIndex);
if (!this.imageId) {
return false;
}
var study = cornerstoneTools.metaData.get('study', this.imageId);
if (!study) {
return '';
}
return study[property];
}
function getSeries(property) {
Session.get('CornerstoneNewImage' + this.viewportIndex);
if (!this.imageId) {
return false;
}
var series = cornerstoneTools.metaData.get('series', this.imageId);
if (!series) {
return '';
}
return series[property];
}
function getInstance(property) {
Session.get('CornerstoneNewImage' + this.viewportIndex);
if (!this.imageId) {
return false;
}
var instance = cornerstoneTools.metaData.get('instance', this.imageId);
if (!instance) {
return '';
}
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) {
return false;
}
var enabledElement;
try {
enabledElement = cornerstone.getEnabledElement(element);
} catch(error) {
return false;
}
if (!enabledElement || !enabledElement.image) {
return false;
}
return enabledElement.image;
}
function formatDateTime(date, time) {
return `${date} ${time}`;
}
import { Template } from 'meteor/templating';
import { Session } from 'meteor/session';
import { viewportOverlayUtils } from '../../../lib/viewportOverlayUtils';
import { getElementIfNotEmpty } from '../../../lib/getElementIfNotEmpty';
import { getStackDataIfNotEmpty } from '../../../lib/getStackDataIfNotEmpty';
Template.viewportOverlay.helpers({
wwwc: function() {
wwwc() {
Session.get('CornerstoneImageRendered' + this.viewportIndex);
var element = getElementIfNotEmpty(this.viewportIndex);
const element = getElementIfNotEmpty(this.viewportIndex);
if (!element) {
return '';
}
var viewport = cornerstone.getViewport(element);
const viewport = cornerstone.getViewport(element);
if (!viewport) {
return '';
}
return 'W ' + viewport.voi.windowWidth.toFixed(0) + ' L ' + viewport.voi.windowCenter.toFixed(0);
},
zoom: function() {
zoom() {
Session.get('CornerstoneImageRendered' + this.viewportIndex);
var element = getElementIfNotEmpty(this.viewportIndex);
const element = getElementIfNotEmpty(this.viewportIndex);
if (!element) {
return;
}
var viewport = cornerstone.getViewport(element);
const viewport = cornerstone.getViewport(element);
if (!viewport) {
return;
}
return (viewport.scale * 100.0);
},
imageDimensions: function() {
imageDimensions() {
Session.get('CornerstoneNewImage' + this.viewportIndex);
var image = getImage(this.viewportIndex);
const image = viewportOverlayUtils.getImage(this.viewportIndex);
if (!image) {
return '';
}
return image.width + ' x ' + image.height;
},
patientName: function() {
return getPatient.call(this, 'name');
patientName() {
Session.get('CornerstoneNewImage' + this.viewportIndex);
return viewportOverlayUtils.getPatient.call(this, 'name');
},
patientId: function() {
return getPatient.call(this, 'id');
patientId() {
Session.get('CornerstoneNewImage' + this.viewportIndex);
return viewportOverlayUtils.getPatient.call(this, 'id');
},
patientBirthDate: function() {
return getPatient.call(this, 'birthDate');
patientBirthDate() {
Session.get('CornerstoneNewImage' + this.viewportIndex);
return viewportOverlayUtils.getPatient.call(this, 'birthDate');
},
patientSex: function() {
return getPatient.call(this, 'sex');
patientSex() {
Session.get('CornerstoneNewImage' + this.viewportIndex);
return viewportOverlayUtils.getPatient.call(this, 'sex');
},
studyDate: function() {
return getStudy.call(this, 'studyDate');
studyDate() {
Session.get('CornerstoneNewImage' + this.viewportIndex);
return viewportOverlayUtils.getStudy.call(this, 'studyDate');
},
studyTime: function() {
return getStudy.call(this, 'studyTime');
studyTime() {
Session.get('CornerstoneNewImage' + this.viewportIndex);
return viewportOverlayUtils.getStudy.call(this, 'studyTime');
},
studyDescription: function() {
return getStudy.call(this, 'studyDescription');
studyDescription() {
Session.get('CornerstoneNewImage' + this.viewportIndex);
return viewportOverlayUtils.getStudy.call(this, 'studyDescription');
},
seriesDescription: function() {
return getSeries.call(this, 'seriesDescription');
seriesDescription() {
Session.get('CornerstoneNewImage' + this.viewportIndex);
return viewportOverlayUtils.getSeries.call(this, 'seriesDescription');
},
frameRate: function() {
var frameTime = getInstance.call(this, 'frameTime');
frameRate() {
Session.get('CornerstoneNewImage' + this.viewportIndex);
const frameTime = viewportOverlayUtils.getInstance.call(this, 'frameTime');
if (!frameTime) {
return;
}
var frameRate = 1000 / frameTime;
const frameRate = 1000 / frameTime;
return frameRate.toFixed(1);
},
seriesNumber: function() {
return getSeries.call(this, 'seriesNumber');
seriesNumber() {
Session.get('CornerstoneNewImage' + this.viewportIndex);
return viewportOverlayUtils.getSeries.call(this, 'seriesNumber');
},
instanceNumber: function() {
return getInstance.call(this, 'instanceNumber');
instanceNumber() {
Session.get('CornerstoneNewImage' + this.viewportIndex);
return viewportOverlayUtils.getInstance.call(this, 'instanceNumber');
},
thickness() {
// Displays Slice Thickness (0018,0050)
return getInstance.call(this, 'sliceThickness');
},
Session.get('CornerstoneNewImage' + this.viewportIndex);
return viewportOverlayUtils.getInstance.call(this, 'sliceThickness');
},
location() {
// Displays Slice Location (0020,1041), if present.
// - Otherwise, displays Table Position (0018,9327)
// - TODO: Otherwise, displays a value derived from Image Position (Patient) (0020,0032)
const sliceLocation = getInstance.call(this, 'sliceLocation');
Session.get('CornerstoneNewImage' + this.viewportIndex);
const sliceLocation = viewportOverlayUtils.getInstance.call(this, 'sliceLocation');
if (sliceLocation !== '') {
return sliceLocation;
}
const tablePosition = getInstance.call(this, 'tablePosition');
const tablePosition = viewportOverlayUtils.getInstance.call(this, 'tablePosition');
if (tablePosition !== '') {
return tablePosition;
}
return getInstance.call(this, 'imagePositionPatient');
return viewportOverlayUtils.getInstance.call(this, 'imagePositionPatient');
},
spacingBetweenSlices() {
// Displays Spacing Between Slices (0018,0088), if present.
// TODO: Otherwise, displays a value derived from successive values
// of Image Position (Patient) (0020,0032) perpendicular to
// the Image Orientation (Patient) (0020,0037)
return getInstance.call(this, 'spacingBetweenSlices');
},
Session.get('CornerstoneNewImage' + this.viewportIndex);
return viewportOverlayUtils.getInstance.call(this, 'spacingBetweenSlices');
},
compression() {
// Displays whether or not lossy compression has been applied:
//
@ -256,11 +150,12 @@ Template.viewportOverlay.helpers({
// and Lossy Image Compression Method (0028,2114)
Session.get('CornerstoneNewImage' + this.viewportIndex);
if (!this.imageId) {
return false;
}
var instance = cornerstoneTools.metaData.get('instance', this.imageId);
const instance = cornerstoneTools.metaData.get('instance', this.imageId);
if (!instance) {
return '';
}
@ -274,43 +169,56 @@ Template.viewportOverlay.helpers({
return 'Lossless / Uncompressed';
},
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() {
tagDisplayLeftOnly() {
Session.get('CornerstoneNewImage' + this.viewportIndex);
var stack = getStackDataIfNotEmpty(this.viewportIndex);
return viewportOverlayUtils.getTagDisplay.call(this, 'side') === 'L';
},
tagDisplayRightOnly() {
Session.get('CornerstoneNewImage' + this.viewportIndex);
return viewportOverlayUtils.getTagDisplay.call(this, 'side') === 'R';
},
tagDisplaySpecified() {
Session.get('CornerstoneNewImage' + this.viewportIndex);
return viewportOverlayUtils.getTagDisplay.call(this, 'side');
},
imageNumber() {
Session.get('CornerstoneNewImage' + this.viewportIndex);
return viewportOverlayUtils.getInstance.call(this, 'number');
},
imageIndex() {
Session.get('CornerstoneNewImage' + this.viewportIndex);
const stack = getStackDataIfNotEmpty(this.viewportIndex);
if (!stack || stack.currentImageIdIndex === undefined) {
return;
}
return stack.currentImageIdIndex + 1;
},
numImages: function() {
numImages() {
Session.get('CornerstoneNewImage' + this.viewportIndex);
var stack = getStackDataIfNotEmpty(this.viewportIndex);
const stack = getStackDataIfNotEmpty(this.viewportIndex);
if (!stack || !stack.imageIds) {
return;
}
return stack.imageIds.length;
},
prior: function() {
prior() {
// This helper is updated whenever a new image is displayed in the viewport
Session.get('CornerstoneNewImage' + this.viewportIndex);
if (!this.imageId) {
return;
}
// Make sure there are more than two studies loaded in the viewer
var viewportStudies = ViewerStudies.find();
const viewportStudies = ViewerStudies.find();
if (viewportStudies.count() < 2) {
return;
}
@ -319,18 +227,18 @@ Template.viewportOverlay.helpers({
// that we can obtain the oldest study as the first element of the array
//
// TODO= Find out if we should encode studyDate as a Date in the ViewerStudies Collection
var viewportStudiesArray = _.sortBy(viewportStudies.fetch(), function(study) {
return formatDateTime(study.studyDate, study.studyTime);
const viewportStudiesArray = _.sortBy(viewportStudies.fetch(), function(study) {
return viewportOverlayUtils.formatDateTime(study.studyDate, study.studyTime);
});
// Get study data
var study = cornerstoneTools.metaData.get('study', this.imageId);
const study = cornerstoneTools.metaData.get('study', this.imageId);
if (!study) {
return;
}
var oldestStudy = viewportStudiesArray[0];
if (formatDateTime(study.studyDate, study.studyTime) <= formatDateTime(oldestStudy.studyDate, oldestStudy.studyTime)) {
const oldestStudy = viewportStudiesArray[0];
if (viewportOverlayUtils.formatDateTime(study.studyDate, study.studyTime) <= viewportOverlayUtils.formatDateTime(oldestStudy.studyDate, oldestStudy.studyTime)) {
return 'Prior';
}
}

View File

@ -185,7 +185,8 @@ import { OHIFError } from './lib/classes/OHIFError';
Viewerbase.OHIFError = OHIFError;
/**
* Imports for Side Effects Only
* Imports for Side Effects Only (Files that do not export anything...)
*/
import './lib/stackImagePositionOffsetSynchronizer.js';
import './lib/debugReactivity';

View File

@ -1,28 +1,86 @@
import { Meteor } from 'meteor/meteor';
import { OHIF } from 'meteor/ohif:core';
import { viewportUtils } from './viewportUtils';
applyWLPreset = function(presetName, element) {
OHIF.log.info("Applying WL Preset: " + presetName);
var viewport = cornerstone.getViewport(element);
// TODO: add this to a namespace definition
Meteor.startup(function() {
OHIF.viewer.defaultWLPresets = {
SoftTissue: {
wc: 40,
ww: 400
},
Lung: {
wc: -600,
ww: 1500
},
Liver: {
wc: 90,
ww: 150
},
Bone: {
wc: 480,
ww: 2500
},
Brain: {
wc: 40,
ww: 80
}
};
if (presetName === 'Default') {
var enabledElement = cornerstone.getEnabledElement(element);
viewport.voi.windowWidth = enabledElement.image.windowWidth;
viewport.voi.windowCenter = enabledElement.image.windowCenter;
} else {
var preset = OHIF.viewer.wlPresets[presetName];
viewport.voi.windowWidth = preset.ww;
viewport.voi.windowCenter = preset.wc;
// For now
OHIF.viewer.wlPresets = OHIF.viewer.defaultWLPresets;
});
function applyWLPreset(presetName, element) {
OHIF.log.info('Applying WL Preset: ' + presetName);
if (presetName !== 'Custom') {
const viewport = cornerstone.getViewport(element);
if (presetName === 'Default') {
const enabledElement = cornerstone.getEnabledElement(element);
viewport.voi.windowWidth = enabledElement.image.windowWidth;
viewport.voi.windowCenter = enabledElement.image.windowCenter;
} else {
const preset = OHIF.viewer.wlPresets[presetName];
if(preset) {
viewport.voi.windowWidth = preset.ww;
viewport.voi.windowCenter = preset.wc;
}
}
// Update the viewport
cornerstone.setViewport(element, viewport);
}
}
// Update the viewport
cornerstone.setViewport(element, viewport);
};
applyWLPresetToActiveElement = function(presetName) {
var element = getActiveViewportElement();
function applyWLPresetToActiveElement(presetName) {
const element = viewportUtils.getActiveViewportElement();
if (!element) {
return;
}
applyWLPreset(presetName, element);
// To perform reactivity in other components
Session.set('WLPresetActiveElement', presetName);
}
/**
* Overrides OHIF's wlPresets
* @param {Object} wlPresets Object with wlPresets mapping
*/
function setOHIFWLPresets(wlPresets) {
OHIF.viewer.wlPresets = wlPresets;
}
/**
* Export functions inside WLPresets namespace.
*/
const WLPresets = {
applyWLPreset,
applyWLPresetToActiveElement,
setOHIFWLPresets
};
export { WLPresets };

View File

@ -1,3 +1,6 @@
import { ImageSet } from './classes/ImageSet';
import { isImage } from './isImage';
/**
* Creates a set of series to be placed in the Study Browser
* The series that appear in the Study Browser must represent
@ -10,7 +13,7 @@
* @param study The study instance to be used
* @returns {Array} An array of series to be placed in the Study Browser
*/
createStacks = function(study) {
function createStacks(study) {
// Define an empty array of display sets
var displaySets = [];
@ -38,33 +41,21 @@ createStacks = function(study) {
let displaySet;
if (isMultiFrame(instance)) {
displaySet = makeDisplaySet(series, [ instance ]);
displaySet.isClip = true;
// Include the study instance Uid for drag/drop purposes
displaySet.studyInstanceUid = study.studyInstanceUid;
// Override the default value of instances.length
displaySet.numImageFrames = instance.numFrames;
// Include the instance number
displaySet.instanceNumber = instance.instanceNumber;
// Include the acquisition datetime
displaySet.acquisitionDatetime = instance.acquisitionDatetime;
displaySet.setAttributes({
isClip: true,
studyInstanceUid: study.studyInstanceUid, // Include the study instance Uid for drag/drop purposes
numImageFrames: instance.numFrames, // Override the default value of instances.length
instanceNumber: instance.instanceNumber, // Include the instance number
acquisitionDatetime: instance.acquisitionDatetime // Include the acquisition datetime
});
displaySets.push(displaySet);
} else if (isSingleImageModality(instance.modality)) {
displaySet = makeDisplaySet(series, [ instance ]);
// Include the study instance Uid
displaySet.studyInstanceUid = study.studyInstanceUid;
// Include the instance number
displaySet.instanceNumber = instance.instanceNumber;
// Include the acquisition datetime
displaySet.acquisitionDatetime = instance.acquisitionDatetime;
displaySet.setAttributes({
studyInstanceUid: study.studyInstanceUid, // Include the study instance Uid
instanceNumber: instance.instanceNumber, // Include the instance number
acquisitionDatetime: instance.acquisitionDatetime // Include the acquisition datetime
});
displaySets.push(displaySet);
} else {
stackableInstances.push(instance);
@ -73,7 +64,7 @@ createStacks = function(study) {
if (stackableInstances.length) {
let displaySet = makeDisplaySet(series, stackableInstances);
displaySet.studyInstanceUid = study.studyInstanceUid;
displaySet.setAttribute('studyInstanceUid', study.studyInstanceUid);
displaySets.push(displaySet);
}
});
@ -84,32 +75,32 @@ createStacks = function(study) {
function makeDisplaySet(series, instances) {
const instance = instances[0];
let displaySet = {
const imageSet = new ImageSet(instances);
// set appropriate attributes to image set...
imageSet.setAttributes({
displaySetInstanceUid: imageSet.uid, // create a local alias for the imageSet UID
seriesInstanceUid: series.seriesInstanceUid,
seriesNumber: series.seriesNumber,
seriesDescription: series.seriesDescription,
numImageFrames: instances.length,
frameRate: instance.frameTime,
images: instances,
modality: series.modality,
isMultiFrame: isMultiFrame(instance)
};
});
// Sort the images in this series
displaySet.images.sort(function(a, b) {
imageSet.sortBy(function(a, b) {
if (a.instanceNumber && b.instanceNumber &&
a.instanceNumber !== b.instanceNumber) {
return a.instanceNumber - b.instanceNumber;
}
});
// Include the first image instance number
displaySet.instanceNumber = displaySet.images[0].instanceNumber;
// Include the first image instance number (after sorted)
imageSet.setAttribute('instanceNumber', imageSet.getImage(0).instanceNumber);
// Create a unique ID for this stack so we can reference it
displaySet.displaySetInstanceUid = Random.id();
return displaySet;
return imageSet;
}
function isSingleImageModality(modality) {
@ -121,3 +112,9 @@ function isSingleImageModality(modality) {
function isMultiFrame(instance) {
return instance.numFrames > 1;
}
/**
* Expose "createStacks"...
*/
export { createStacks };

View File

@ -7,21 +7,30 @@ import { OHIF } from 'meteor/ohif:core';
*
* @param element {node} DOM Node representing the viewport element
*/
displayReferenceLines = element => {
OHIF.log.info("imageViewerViewport displayReferenceLines");
export function displayReferenceLines(element) {
// Check if image plane (orientation / loction) data is present for the current image
const enabledElement = cornerstone.getEnabledElement(element);
const imageId = enabledElement.image.imageId;
const imagePlane = cornerstoneTools.metaData.get('imagePlane', imageId);
if (!OHIF.viewer.refLinesEnabled || !imagePlane || !imagePlane.frameOfReferenceUID) {
// Check if element is already enabled and it's image was rendered
if(!enabledElement || !enabledElement.image) {
OHIF.log.info('displayReferenceLines enabled element is undefined or it\'s image is not rendered');
return;
}
const imageId = enabledElement.image.imageId;
const imagePlane = cornerstoneTools.metaData.get('imagePlane', imageId);
// Disable reference lines for the current element
cornerstoneTools.referenceLines.tool.disable(element);
if (!OHIF.viewer.refLinesEnabled || !imagePlane || !imagePlane.frameOfReferenceUID) {
OHIF.log.info('displayReferenceLines refLinesEnabled is not enabled, no imagePlane or no frameOfReferenceUID');
return;
}
OHIF.log.info(`displayReferenceLines for image with id: ${imageId}`);
// Loop through all other viewport elements and enable reference lines
$('.imageViewerViewport').not(element).each((index, viewportElement) => {
let imageId;
@ -40,4 +49,4 @@ displayReferenceLines = element => {
cornerstoneTools.referenceLines.tool.enable(viewportElement, OHIF.viewer.updateImageSynchronizer);
}
});
};
}

View File

@ -1,4 +1,5 @@
import { Meteor } from "meteor/meteor";
import { getWADORSImageId } from './getWADORSImageId';
/**
* Obtain an imageId for Cornerstone from an image instance
@ -6,17 +7,17 @@ import { Meteor } from "meteor/meteor";
* @param instance
* @returns {string} The imageId to be used by Cornerstone
*/
getImageId = function(instance, frame) {
export function getImageId(instance, frame) {
if (!instance) {
return;
}
if (instance.url) {
return instance.url;
return instance.url;
}
if (instance.wadouri) {
var imageId = 'dicomweb:' + Meteor.absoluteUrl(instance.wadouri); // WADO-URI;
var imageId = 'dicomweb:' + Meteor.absoluteUrl(instance.wadouri);
if (frame !== undefined) {
imageId += '&frame=' + frame;
}
@ -26,4 +27,4 @@ getImageId = function(instance, frame) {
// TODO= Check multiframe image support with WADO-RS
return getWADORSImageId(instance); // WADO-RS Retrieve Frame
}
};
}

View File

@ -8,7 +8,7 @@ import { OHIF } from 'meteor/ohif:core';
* @returns {string} The imageId to be used by Cornerstone
*/
getWADORSImageId = function(instance) {
export function getWADORSImageId(instance) {
var columnPixelSpacing = 1.0;
var rowPixelSpacing = 1.0;
if (instance.pixelSpacing) {

View File

@ -1,3 +1,5 @@
import { Blaze } from 'meteor/blaze';
/**
* A global Blaze UI helper to capitalizes the first letter of an input String
*
@ -5,10 +7,10 @@
*
* http://stackoverflow.com/questions/1026069/capitalize-the-first-letter-of-string-in-javascript
*/
UI.registerHelper('capitalizeFirstLetter', function (context) {
Blaze.registerHelper('capitalizeFirstLetter', function (context) {
if (!context) {
return;
}
return context.charAt(0).toUpperCase() + context.slice(1);
});
});

View File

@ -1,7 +1,11 @@
import { Blaze } from 'meteor/blaze';
import { moment } from 'meteor/momentjs:moment';
/**
* A global Blaze UI helper function to format DICOM Dates using the Moment library
*/
UI.registerHelper('formatDA', function (context, format, options) {
const formatDA = (context, format, options) => {
if (!context) {
return undefined;
}
@ -11,4 +15,8 @@ UI.registerHelper('formatDA', function (context, format, options) {
strFormat = format;
}
return dateAsMoment.format(strFormat);
});
};
Blaze.registerHelper('formatDA', formatDA);
export { formatDA };

View File

@ -1,7 +1,10 @@
import { Blaze } from 'meteor/blaze';
import { moment } from 'meteor/momentjs:moment';
/**
* A global Blaze UI helper function to format JavaScript Dates using the Moment library
*/
UI.registerHelper('formatJSDate', function(context, format, options) {
Blaze.registerHelper('formatJSDate', function(context, format, options) {
if (!context) {
return;
}

View File

@ -1,8 +1,10 @@
import { Blaze } from 'meteor/blaze';
/**
* A global Blaze UI helper to format a float value to a specified precision
*/
UI.registerHelper('formatNumberPrecision', function(context, precision) {
Blaze.registerHelper('formatNumberPrecision', function(context, precision) {
if (context != null) {
return parseFloat(context).toFixed(precision);
}
});
});

View File

@ -1,7 +1,8 @@
import { Blaze } from 'meteor/blaze';
/**
* Formats a patient name for display purposes
*/
formatPN = function(context) {
const formatPN = context => {
if (!context) {
return;
}
@ -11,4 +12,6 @@ formatPN = function(context) {
/**
* A global Blaze UI helper to format a patient name for display purposes
*/
UI.registerHelper('formatPN', formatPN);
Blaze.registerHelper('formatPN', formatPN);
export { formatPN };

View File

@ -1,7 +1,11 @@
import { Blaze } from 'meteor/blaze';
import { moment } from 'meteor/momentjs:moment';
/**
* A global Blaze UI helper to format a DICOM Time for display using the Moment library
*/
UI.registerHelper('formatTM', function(context, options) {
const formatTM = (context, options) => {
if (!context) {
return;
}
@ -21,4 +25,8 @@ UI.registerHelper('formatTM', function(context, options) {
}
return dateTime.format(format);
});
};
Blaze.registerHelper('formatTM', formatTM);
export { formatTM };

View File

@ -1,7 +1,10 @@
import { Meteor } from 'meteor/meteor';
import { Blaze } from 'meteor/blaze';
/**
* Helper for retrieving username given userId
*/
UI.registerHelper('getUsername', function(userId) {
Blaze.registerHelper('getUsername', function(userId) {
var user = Meteor.users.findOne({
userId: userId
});

View File

@ -1,7 +1,9 @@
import { Blaze } from 'meteor/blaze';
/**
* Helper for checking datatype of a variable
*/
UI.registerHelper('ifTypeIs', function(value, match, attributeName) {
Blaze.registerHelper('ifTypeIs', function(value, match, attributeName) {
if (typeof(value) === match) {
return attributeName;
}

View File

@ -1,3 +1,5 @@
UI.registerHelper('inc', function(value) {
import { Blaze } from 'meteor/blaze';
Blaze.registerHelper('inc', function(value) {
return parseInt(value) + 1;
});

View File

@ -1,3 +1,11 @@
import { Template } from 'meteor/templating';
import { Session } from 'meteor/session';
import { $ } from 'meteor/jquery';
import { viewportUtils } from '../viewportUtils';
import { OHIF } from 'meteor/ohif:core';
import 'meteor/ohif:viewerbase';
/**
* Boolean helper to identify if a series instance is active in some viewport
*/
@ -6,7 +14,7 @@ Template.registerHelper('isDisplaySetActive', (displaySetInstanceUid, viewportIn
Session.get('LayoutManagerUpdated');
// Stop here if layoutManager is not defined yet
if (!window.layoutManager) {
if (!OHIF.viewerbase.layoutManager) {
return;
}
@ -22,22 +30,28 @@ Template.registerHelper('isDisplaySetActive', (displaySetInstanceUid, viewportIn
if (_.isUndefined(viewportIndex)) {
// Get the number of viewports that are currently displayed
// (Note, viewportData may have more entries!)
const currentNumberOfViewports = layoutManager.getNumberOfViewports();
const currentNumberOfViewports = OHIF.viewerbase.layoutManager.getNumberOfViewports();
// Get active viewport element
const activeViewport = viewportUtils.getActiveViewportElement();
// Get viewports elements
const viewportsElement = $('.imageViewerViewport');
// Loop through the viewport data up until the currently displayed
// number of viewports
let viewportData = window.layoutManager.viewportData;
const viewportData = OHIF.viewerbase.layoutManager.viewportData;
for (let i = 0; i < currentNumberOfViewports; i++) {
const data = viewportData[i];
// If the display set is displayed in this viewport, stop here
if (data && data.displaySetInstanceUid === displaySetInstanceUid) {
// If the display set is displayed in this viewport and is active, stop here
if (data && data.displaySetInstanceUid === displaySetInstanceUid && viewportsElement.get(data.viewportIndex) === activeViewport) {
result = true;
break;
}
}
} else {
const data = window.layoutManager.viewportData[viewportIndex];
const data = OHIF.viewerbase.layoutManager.viewportData[viewportIndex];
// If the display set is displayed in this viewport, stop here
if (data && data.displaySetInstanceUid === displaySetInstanceUid) {

View File

@ -1,3 +1,5 @@
import { Blaze } from 'meteor/blaze';
/**
* Helper function to determine if the current client devices
* is touch-capable. This can be used to modify certain aspects of the UI.
@ -6,7 +8,7 @@
*
* @returns {boolean} true if the client device is touch-capable, false otherwise
*/
isTouchDevice = function() {
const isTouchDevice = () => {
return (('ontouchstart' in window) ||
(navigator.MaxTouchPoints > 0) ||
(navigator.msMaxTouchPoints > 0));
@ -17,4 +19,6 @@ isTouchDevice = function() {
*
* @returns {boolean} true if the client device is touch-capable, false otherwise
*/
UI.registerHelper('isTouchDevice', isTouchDevice);
Blaze.registerHelper('isTouchDevice', isTouchDevice);
export { isTouchDevice };

View File

@ -1,7 +1,11 @@
import { Blaze } from 'meteor/blaze';
import { Session } from 'meteor/session';
import { moment } from 'meteor/momentjs:moment';
/**
* A global Blaze UI helper function to format JavaScript Dates using the Moment library
*/
UI.registerHelper('jsDateFromNow', function(context, format, options) {
Blaze.registerHelper('jsDateFromNow', function(context, format, options) {
if (!context) {
return;
}

View File

@ -1,4 +1,7 @@
UI.registerHelper('objectEach', function(object) {
import { Blaze } from 'meteor/blaze';
import { _ } from 'meteor/underscore';
Blaze.registerHelper('objectEach', function(object) {
// http://stackoverflow.com/questions/30234732/how-to-print-key-and-values-in-meteor-template
return _.map(object, function(value, key) {
return _.extend({key: key}, value);

View File

@ -1,4 +1,7 @@
UI.registerHelper('objectToPairs', function(object) {
import { Blaze } from 'meteor/blaze';
import { _ } from 'meteor/underscore';
Blaze.registerHelper('objectToPairs', function(object) {
// http://stackoverflow.com/questions/30234732/how-to-print-key-and-values-in-meteor-template
return _.map(object, function(value, key) {
return {

View File

@ -1,3 +1,5 @@
import { Blaze } from 'meteor/blaze';
/**
* A global Blaze UI helper to Stringify a JavaScript object
*
@ -5,7 +7,7 @@
*
* http://stackoverflow.com/questions/1026069/capitalize-the-first-letter-of-string-in-javascript
*/
UI.registerHelper('prettyPrintStringify', function(context) {
Blaze.registerHelper('prettyPrintStringify', function(context) {
if (!context) {
return;
}

View File

@ -1,5 +1,5 @@
import { _ } from 'meteor/underscore';
import { Template } from 'meteor/templating';
import { _ } from 'meteor/underscore';
/**
* Global Blaze UI helper to sort array elements
@ -54,4 +54,4 @@ function getKeyValue(object, keyPath) {
}
}
return object;
}
}

View File

@ -1,3 +1,6 @@
import { Template } from 'meteor/templating';
import { _ } from 'meteor/underscore';
/**
* A global Blaze UI helper to get the thumbnails for the given study
*/

View File

@ -1,6 +1,8 @@
var instanceClassViewportSettingsFunctions = {};
getInstanceClassDefaultViewport = function(series, enabledElement, imageId) {
var instanceClass = series.sopClassUid;
const instanceClassViewportSettingsFunctions = {};
const getInstanceClassDefaultViewport = (series, enabledElement, imageId) => {
let instanceClass = series.sopClassUid;
if (!instanceClassViewportSettingsFunctions[instanceClass]) {
return;
@ -9,6 +11,8 @@ getInstanceClassDefaultViewport = function(series, enabledElement, imageId) {
return instanceClassViewportSettingsFunctions[instanceClass](series, enabledElement, imageId);
};
setInstanceClassDefaultViewportFunction = function(instanceClass, fn) {
const setInstanceClassDefaultViewportFunction = (instanceClass, fn) => {
instanceClassViewportSettingsFunctions[instanceClass] = fn;
};
};
export { getInstanceClassDefaultViewport, setInstanceClassDefaultViewportFunction };

View File

@ -1,59 +1,61 @@
import { sopClassDictionary } from './sopClassDictionary';
/**
* Checks whether dicom files with specified SOP Class UID have image data
* @param {string} sopClassUid - SOP Class UID to be checked
* @returns {boolean} - true if it has image data
*/
isImage = function(sopClassUid) {
if (sopClassUid == sopClassDictionary.ComputedRadiographyImageStorage
|| sopClassUid == sopClassDictionary.DigitalXRayImageStorageForPresentation
|| sopClassUid == sopClassDictionary.DigitalXRayImageStorageForProcessing
|| sopClassUid == sopClassDictionary.DigitalMammographyXRayImageStorageForPresentation
|| sopClassUid == sopClassDictionary.DigitalMammographyXRayImageStorageForProcessing
|| sopClassUid == sopClassDictionary.DigitalIntraOralXRayImageStorageForPresentation
|| sopClassUid == sopClassDictionary.DigitalIntraOralXRayImageStorageForProcessing
|| sopClassUid == sopClassDictionary.CTImageStorage
|| sopClassUid == sopClassDictionary.EnhancedCTImageStorage
|| sopClassUid == sopClassDictionary.LegacyConvertedEnhancedCTImageStorage
|| sopClassUid == sopClassDictionary.UltrasoundMultiframeImageStorage
|| sopClassUid == sopClassDictionary.MRImageStorage
|| sopClassUid == sopClassDictionary.EnhancedMRImageStorage
|| sopClassUid == sopClassDictionary.EnhancedMRColorImageStorage
|| sopClassUid == sopClassDictionary.LegacyConvertedEnhancedMRImageStorage
|| sopClassUid == sopClassDictionary.UltrasoundImageStorage
|| sopClassUid == sopClassDictionary.SecondaryCaptureImageStorage
|| sopClassUid == sopClassDictionary.MultiframeSingleBitSecondaryCaptureImageStorage
|| sopClassUid == sopClassDictionary.MultiframeGrayscaleByteSecondaryCaptureImageStorage
|| sopClassUid == sopClassDictionary.MultiframeGrayscaleWordSecondaryCaptureImageStorage
|| sopClassUid == sopClassDictionary.MultiframeTrueColorSecondaryCaptureImageStorage
|| sopClassUid == sopClassDictionary.XRayAngiographicImageStorage
|| sopClassUid == sopClassDictionary.EnhancedXAImageStorage
|| sopClassUid == sopClassDictionary.XRayRadiofluoroscopicImageStorage
|| sopClassUid == sopClassDictionary.EnhancedXRFImageStorage
|| sopClassUid == sopClassDictionary.XRay3DAngiographicImageStorage
|| sopClassUid == sopClassDictionary.XRay3DCraniofacialImageStorage
|| sopClassUid == sopClassDictionary.BreastTomosynthesisImageStorage
|| sopClassUid == sopClassDictionary.BreastProjectionXRayImageStorageForPresentation
|| sopClassUid == sopClassDictionary.BreastProjectionXRayImageStorageForProcessing
|| sopClassUid == sopClassDictionary.IntravascularOpticalCoherenceTomographyImageStorageForPresentation
|| sopClassUid == sopClassDictionary.IntravascularOpticalCoherenceTomographyImageStorageForProcessing
|| sopClassUid == sopClassDictionary.NuclearMedicineImageStorage
|| sopClassUid == sopClassDictionary.VLEndoscopicImageStorage
|| sopClassUid == sopClassDictionary.VideoEndoscopicImageStorage
|| sopClassUid == sopClassDictionary.VLMicroscopicImageStorage
|| sopClassUid == sopClassDictionary.VideoMicroscopicImageStorage
|| sopClassUid == sopClassDictionary.VLSlideCoordinatesMicroscopicImageStorage
|| sopClassUid == sopClassDictionary.VLPhotographicImageStorage
|| sopClassUid == sopClassDictionary.VideoPhotographicImageStorage
|| sopClassUid == sopClassDictionary.OphthalmicPhotography8BitImageStorage
|| sopClassUid == sopClassDictionary.OphthalmicPhotography16BitImageStorage
|| sopClassUid == sopClassDictionary.OphthalmicTomographyImageStorage
|| sopClassUid == sopClassDictionary.VLWholeSlideMicroscopyImageStorage
|| sopClassUid == sopClassDictionary.PositronEmissionTomographyImageStorage
|| sopClassUid == sopClassDictionary.EnhancedPETImageStorage
|| sopClassUid == sopClassDictionary.LegacyConvertedEnhancedPETImageStorage
|| sopClassUid == sopClassDictionary.RTImageStorage) {
export function isImage(sopClassUid) {
if (sopClassUid === sopClassDictionary.ComputedRadiographyImageStorage
|| sopClassUid === sopClassDictionary.DigitalXRayImageStorageForPresentation
|| sopClassUid === sopClassDictionary.DigitalXRayImageStorageForProcessing
|| sopClassUid === sopClassDictionary.DigitalMammographyXRayImageStorageForPresentation
|| sopClassUid === sopClassDictionary.DigitalMammographyXRayImageStorageForProcessing
|| sopClassUid === sopClassDictionary.DigitalIntraOralXRayImageStorageForPresentation
|| sopClassUid === sopClassDictionary.DigitalIntraOralXRayImageStorageForProcessing
|| sopClassUid === sopClassDictionary.CTImageStorage
|| sopClassUid === sopClassDictionary.EnhancedCTImageStorage
|| sopClassUid === sopClassDictionary.LegacyConvertedEnhancedCTImageStorage
|| sopClassUid === sopClassDictionary.UltrasoundMultiframeImageStorage
|| sopClassUid === sopClassDictionary.MRImageStorage
|| sopClassUid === sopClassDictionary.EnhancedMRImageStorage
|| sopClassUid === sopClassDictionary.EnhancedMRColorImageStorage
|| sopClassUid === sopClassDictionary.LegacyConvertedEnhancedMRImageStorage
|| sopClassUid === sopClassDictionary.UltrasoundImageStorage
|| sopClassUid === sopClassDictionary.SecondaryCaptureImageStorage
|| sopClassUid === sopClassDictionary.MultiframeSingleBitSecondaryCaptureImageStorage
|| sopClassUid === sopClassDictionary.MultiframeGrayscaleByteSecondaryCaptureImageStorage
|| sopClassUid === sopClassDictionary.MultiframeGrayscaleWordSecondaryCaptureImageStorage
|| sopClassUid === sopClassDictionary.MultiframeTrueColorSecondaryCaptureImageStorage
|| sopClassUid === sopClassDictionary.XRayAngiographicImageStorage
|| sopClassUid === sopClassDictionary.EnhancedXAImageStorage
|| sopClassUid === sopClassDictionary.XRayRadiofluoroscopicImageStorage
|| sopClassUid === sopClassDictionary.EnhancedXRFImageStorage
|| sopClassUid === sopClassDictionary.XRay3DAngiographicImageStorage
|| sopClassUid === sopClassDictionary.XRay3DCraniofacialImageStorage
|| sopClassUid === sopClassDictionary.BreastTomosynthesisImageStorage
|| sopClassUid === sopClassDictionary.BreastProjectionXRayImageStorageForPresentation
|| sopClassUid === sopClassDictionary.BreastProjectionXRayImageStorageForProcessing
|| sopClassUid === sopClassDictionary.IntravascularOpticalCoherenceTomographyImageStorageForPresentation
|| sopClassUid === sopClassDictionary.IntravascularOpticalCoherenceTomographyImageStorageForProcessing
|| sopClassUid === sopClassDictionary.NuclearMedicineImageStorage
|| sopClassUid === sopClassDictionary.VLEndoscopicImageStorage
|| sopClassUid === sopClassDictionary.VideoEndoscopicImageStorage
|| sopClassUid === sopClassDictionary.VLMicroscopicImageStorage
|| sopClassUid === sopClassDictionary.VideoMicroscopicImageStorage
|| sopClassUid === sopClassDictionary.VLSlideCoordinatesMicroscopicImageStorage
|| sopClassUid === sopClassDictionary.VLPhotographicImageStorage
|| sopClassUid === sopClassDictionary.VideoPhotographicImageStorage
|| sopClassUid === sopClassDictionary.OphthalmicPhotography8BitImageStorage
|| sopClassUid === sopClassDictionary.OphthalmicPhotography16BitImageStorage
|| sopClassUid === sopClassDictionary.OphthalmicTomographyImageStorage
|| sopClassUid === sopClassDictionary.VLWholeSlideMicroscopyImageStorage
|| sopClassUid === sopClassDictionary.PositronEmissionTomographyImageStorage
|| sopClassUid === sopClassDictionary.EnhancedPETImageStorage
|| sopClassUid === sopClassDictionary.LegacyConvertedEnhancedPETImageStorage
|| sopClassUid === sopClassDictionary.RTImageStorage) {
return true;
}
return false;
};
}

View File

@ -1,6 +1,7 @@
import {parsingUtils} from './parsingUtils'
import { Meteor } from 'meteor/meteor';
import { parsingUtils } from './parsingUtils';
var metaDataLookup = {};
const metaDataLookup = {};
/**
* Cornerstone MetaData provider to store image meta data
@ -14,14 +15,14 @@ var metaDataLookup = {};
* @param {String} imageId The Cornerstone ImageId
* @param {Object} data An object containing instance, series, and study metaData
*/
addMetaData = function(imageId, data) {
var instanceMetaData = data.instance;
var seriesMetaData = data.series;
var studyMetaData = data.study;
var imageIndex = data.imageIndex;
var numImages = data.numImages;
const addMetaData = (imageId, data) => {
let instanceMetaData = data.instance;
let seriesMetaData = data.series;
let studyMetaData = data.study;
let imageIndex = data.imageIndex;
let numImages = data.numImages;
var metaData = {};
let metaData = {};
metaData.study = {
patientId: studyMetaData.patientId,
@ -66,8 +67,8 @@ addMetaData = function(imageId, data) {
* @param type (e.g. series, instance, tagDisplay)
* @param data
*/
addSpecificMetadata = function(imageId, type, data) {
var metaData = {};
const addSpecificMetadata = (imageId, type, data) => {
let metaData = {};
metaData[type] = data;
metaDataLookup[imageId] = $.extend(metaDataLookup[imageId], metaData);
@ -92,8 +93,8 @@ function getFromDataSet(dataSet, type, tag) {
*
* @param image
*/
updateMetaData = function(image) {
var imageMetaData = metaDataLookup[image.imageId];
const updateMetaData = image => {
const imageMetaData = metaDataLookup[image.imageId];
if (!imageMetaData) {
return;
}
@ -141,13 +142,13 @@ function getImagePlane(instance) {
return;
}
var imageOrientation = instance.imageOrientationPatient.split('\\');
var imagePosition = instance.imagePositionPatient.split('\\');
let imageOrientation = instance.imageOrientationPatient.split('\\');
let imagePosition = instance.imagePositionPatient.split('\\');
var columnPixelSpacing = 1.0;
var rowPixelSpacing = 1.0;
let columnPixelSpacing = 1.0;
let rowPixelSpacing = 1.0;
if (instance.pixelSpacing) {
var split = instance.pixelSpacing.split('\\');
let split = instance.pixelSpacing.split('\\');
rowPixelSpacing = parseFloat(split[0]);
columnPixelSpacing = parseFloat(split[1]);
}
@ -178,9 +179,9 @@ function getImagePlane(instance) {
* @param dataSet {Object} An instance of dicomParser.DataSet object where multiframe information can be found.
* @return {Object} An object containing multiframe image metadata (frameIncrementPointer, frameTime, frameTimeVector, etc).
*/
getMultiframeModuleMetaData = function(dataSet) {
function getMultiframeModuleMetaData(dataSet) {
var numberOfFrames,
let numberOfFrames,
frameIncrementPointer,
frameTime,
frameTimeVector,
@ -232,7 +233,7 @@ getMultiframeModuleMetaData = function(dataSet) {
}
return imageInfo;
};
}
/**
* Looks up metaData for Cornerstone Tools given a specified type and imageId
@ -244,7 +245,7 @@ getMultiframeModuleMetaData = function(dataSet) {
* @returns {Object} Relevant metaData of the specified type
*/
function provider(type, imageId) {
var imageMetaData = metaDataLookup[imageId];
let imageMetaData = metaDataLookup[imageId];
if (!imageMetaData) {
return;
}
@ -257,3 +258,9 @@ function provider(type, imageId) {
Meteor.startup(function() {
cornerstoneTools.metaData.addProvider(provider);
});
/**
* Export relevant symbols
*/
export { addMetaData, addSpecificMetadata, updateMetaData };

View File

@ -1,14 +1,32 @@
import { Session } from 'meteor/session';
import { $ } from 'meteor/jquery';
import { OHIF } from 'meteor/ohif:core';
import { enablePrefetchOnElement } from './enablePrefetchOnElement';
import { displayReferenceLines } from './displayReferenceLines';
setActiveViewport = element => {
/**
* Sets a viewport element active
* @param {node} element DOM element to be activated
*/
export function setActiveViewport(element) {
if (!element) {
OHIF.log.info('setActiveViewport element does not exist');
return;
}
const viewportIndex = $('.imageViewerViewport').index(element);
const viewerports = $('.imageViewerViewport');
const viewportIndex = viewerports.index(element);
const jQueryElement = $(element);
// When an ActivateViewport event is fired, update the Meteor Session
OHIF.log.info(`setActiveViewport setting viewport index: ${viewportIndex}`);
// If viewport is not active
if(!jQueryElement.parents('.viewportContainer').hasClass('active')) {
// Trigger an event for compatibility with other systems
jQueryElement.trigger('OHIFBeforeActivateViewport');
}
// When an OHIFActivateViewport event is fired, update the Meteor Session
// with the viewport index that it was fired from.
Session.set('activeViewport', viewportIndex);
@ -28,11 +46,19 @@ setActiveViewport = element => {
const domElement = jQueryElement.get(0);
enablePrefetchOnElement(domElement);
displayReferenceLines(domElement);
OHIF.viewer.stackImagePositionOffsetSynchronizer.update();
// @TODO Add this to OHIFAfterActivateViewport handler...
if (OHIF.viewer.stackImagePositionOffsetSynchronizer) {
OHIF.viewer.stackImagePositionOffsetSynchronizer.update();
}
}
// Set the div to focused, so keypress events are handled
//$(element).focus();
//.focus() event breaks in FF&IE
jQueryElement.triggerHandler('focus');
};
// Trigger OHIFAfterActivateViewport event on activated instance
// for compatibility with other systems
jQueryElement.trigger('OHIFAfterActivateViewport');
}

View File

@ -1,19 +1,24 @@
import { Session } from 'meteor/session';
import { $ } from 'meteor/jquery';
/**
* Restores the browser focus to the currently specified active viewport
* as determined from Meteor's Session variable.
*
* This is allows keydown events to be captured on the focused element
* This is allows keydown events to be captured on the focused element.
*/
setFocusToActiveViewport = function() {
const setFocusToActiveViewport = () => {
// Get the list of viewports
var viewports = $('.imageViewerViewport');
const viewports = $('.imageViewerViewport');
// Get the current active viewport index from Session
var activeViewportIndex = Session.get('activeViewport');
const activeViewportIndex = Session.get('activeViewport');
// Find the div from the list of viewports
var activeViewport = viewports.eq(activeViewportIndex);
const activeViewport = viewports.eq(activeViewportIndex);
// Set the browser focus to this div
activeViewport.focus();
};
};
export { setFocusToActiveViewport };

View File

@ -1,19 +1,24 @@
setMammogramViewportAlignment = function(series, enabledElement, imageId) {
// Don't apply the MG viewport alignment to other series types
var viewTypes = ['MLO', 'CC', 'LM', 'ML', 'XCCL'];
var requiresInversion = ['LM']; // Tomo series are flipped
import { Meteor } from 'meteor/meteor';
import { $ } from 'meteor/jquery';
import { setInstanceClassDefaultViewportFunction } from './instanceClassSpecificViewport';
import { addSpecificMetadata } from './metaDataProvider';
var instance = cornerstoneTools.metaData.get('instance', imageId);
const setMammogramViewportAlignment = (series, enabledElement, imageId) => {
// Don't apply the MG viewport alignment to other series types
let viewTypes = ['MLO', 'CC', 'LM', 'ML', 'XCCL'];
let requiresInversion = ['LM']; // Tomo series are flipped
let instance = cornerstoneTools.metaData.get('instance', imageId);
if (!instance) {
return;
}
var laterality = instance.laterality;
var element = enabledElement.element;
var position;
let laterality = instance.laterality;
let element = enabledElement.element;
let position;
var left = $(enabledElement.canvas).offset().left;
var right = left + enabledElement.canvas.width;
let left = $(enabledElement.canvas).offset().left;
let right = left + enabledElement.canvas.width;
if (viewTypes.indexOf(instance.viewPosition) < 0) {
return;
@ -58,4 +63,6 @@ setMammogramViewportAlignment = function(series, enabledElement, imageId) {
Meteor.startup(function() {
setInstanceClassDefaultViewportFunction("1.2.840.10008.5.1.4.1.1.1.2", setMammogramViewportAlignment);
});
});
export { setMammogramViewportAlignment };

View File

@ -1,4 +1,5 @@
sopClassDictionary = {
export const sopClassDictionary = {
ComputedRadiographyImageStorage: "1.2.840.10008.5.1.4.1.1.1",
DigitalXRayImageStorageForPresentation: "1.2.840.10008.5.1.4.1.1.1.1",
DigitalXRayImageStorageForProcessing: "1.2.840.10008.5.1.4.1.1.1.1.1",
@ -112,4 +113,4 @@ sopClassDictionary = {
GenericImplantTemplateStorage: "1.2.840.10008.5.1.4.43.1",
ImplantAssemblyTemplateStorage: "1.2.840.10008.5.1.4.44.1",
ImplantTemplateGroupStorage: "1.2.840.10008.5.1.4.45.1"
};
};

View File

@ -1,21 +1,19 @@
import { OHIFError } from './classes/OHIFError';
/**
* Sorts the series and instances inside a study instance by their series
* and instance numbers in ascending order.
*
* @param {Object} study The study instance
*/
sortStudy = function(study) {
export function sortStudy(study) {
if (!study || !study.seriesList) {
throw "Insufficient study data was provided to sortStudy";
throw new OHIFError('Insufficient study data was provided to sortStudy');
}
study.seriesList.sort(function(a, b) {
return a.seriesNumber - b.seriesNumber;
});
study.seriesList.forEach(function(series){
series.instances.sort(function(a, b) {
return a.instanceNumber - b.instanceNumber;
});
study.seriesList.sort((a, b) => a.seriesNumber - b.seriesNumber);
study.seriesList.forEach(series => {
series.instances.sort((a, b) => a.instanceNumber - b.instanceNumber);
});
};
}

View File

@ -1,3 +1,5 @@
import { viewportUtils } from './viewportUtils';
/**
* This function switches to an image given an element and the index of the image in the current stack
* Note: Negative indexing is supported:
@ -7,7 +9,7 @@
* @param element
* @param {number} [newImageIdIndex] The image index in the stack to switch to.
*/
switchToImageByIndex = function(newImageIdIndex) {
var element = getActiveViewportElement();
export function switchToImageByIndex(newImageIdIndex) {
var element = viewportUtils.getActiveViewportElement();
cornerstoneTools.scrollToIndex(element, newImageIdIndex);
};
}

View File

@ -1,3 +1,5 @@
import { viewportUtils } from './viewportUtils';
/**
* This function switches to an image given an element and
* the relative distance from the current image in the stack
@ -7,7 +9,7 @@
* @param element
* @param {number} [distanceFromCurrentIndex] The image index in the stack to switch to.
*/
switchToImageRelative = function(distanceFromCurrentIndex) {
var element = getActiveViewportElement();
export function switchToImageRelative(distanceFromCurrentIndex) {
var element = viewportUtils.getActiveViewportElement();
cornerstoneTools.scroll(element, distanceFromCurrentIndex);
};
}

View File

@ -1,6 +1,8 @@
updateAllViewports = function() {
import { $ } from 'meteor/jquery';
export function updateAllViewports() {
var viewports = $('.imageViewerViewport').not('.empty');
viewports.each(function(index, element) {
cornerstone.updateImage(element);
});
};
}

View File

@ -3,6 +3,7 @@ import { Random } from 'meteor/random';
import { $ } from 'meteor/jquery';
import { _ } from 'meteor/underscore';
// Local Modules
import { OHIF } from 'meteor/ohif:core';
import { updateOrientationMarkers } from './updateOrientationMarkers';
import { getInstanceClassDefaultViewport } from './instanceClassSpecificViewport';
@ -120,6 +121,16 @@ function clearTools() {
cornerstone.updateImage(element);
}
function linkStackScroll() {
const synchronizer = OHIF.viewer.stackImagePositionOffsetSynchronizer;
if(synchronizer.isActive()) {
synchronizer.deactivate();
} else {
synchronizer.activate();
}
}
// This function was originally defined alone inside client/lib/toggleDialog.js
// and has been moved here to avoid circular dependency issues.
function toggleDialog(element) {
@ -238,6 +249,17 @@ function stopAllClips() {
}
function isStackScrollLinkingDisabled() {
// Its called everytime active viewport and/or layout change
Session.get('activeViewport');
Session.get('LayoutManagerUpdated');
const synchronizer = OHIF.viewer.stackImagePositionOffsetSynchronizer;
const linkableViewports = synchronizer.getLinkableViewports();
return linkableViewports.length <= 1;
}
// Create an event listener to update playing state when a clip stops playing
$(window).on('CornerstoneToolsClipStopped', () => Session.set('UpdateCINE', Random.id()));
@ -257,12 +279,14 @@ const viewportUtils = {
flipH,
resetViewport,
clearTools,
linkStackScroll,
toggleDialog,
toggleCinePlay,
toggleCineDialog,
isPlaying,
hasMultipleFrames,
stopAllClips
stopAllClips,
isStackScrollLinkingDisabled
};
export { viewportUtils };

View File

@ -16,9 +16,10 @@ Package.onUse(function(api) {
api.use('validatejs');
// Our custom packages
api.use('design');
api.use('ohif:design');
api.use('ohif:core');
api.use('ohif:cornerstone');
api.use('ohif:log');
const assets = [
'assets/icons.svg',
@ -137,7 +138,6 @@ Package.onUse(function(api) {
api.addFiles('client/components/viewer/annotationDialogs/annotationDialogs.styl', 'client');
api.addFiles('client/components/viewer/viewportOrientationMarkers/viewportOrientationMarkers.html', 'client');
api.addFiles('client/components/viewer/viewportOrientationMarkers/viewportOrientationMarkers.js', 'client');
api.addFiles('client/components/viewer/viewportOrientationMarkers/viewportOrientationMarkers.styl', 'client');
api.addFiles('client/components/viewer/viewportOverlay/viewportOverlay.html', 'client');
@ -178,6 +178,10 @@ Package.onUse(function(api) {
api.addFiles('client/components/viewer/confirmDeleteDialog/confirmDeleteDialog.js', 'client');
api.addFiles('client/components/viewer/confirmDeleteDialog/confirmDeleteDialog.styl', 'client');
api.addFiles('client/components/viewer/textMarkerDialogs/textMarkerDialogs.html', 'client');
api.addFiles('client/components/viewer/textMarkerDialogs/textMarkerDialogs.js', 'client');
api.addFiles('client/components/viewer/textMarkerDialogs/textMarkerDialogs.styl', 'client');
api.addFiles('client/components/viewer/displaySetNavigation/displaySetNavigation.html', 'client');
api.addFiles('client/components/viewer/displaySetNavigation/displaySetNavigation.js', 'client');
@ -197,105 +201,13 @@ Package.onUse(function(api) {
api.addFiles('client/components/viewer/studyTimepointBrowser/studyTimepointStudy.styl', 'client');
api.addFiles('client/components/viewer/studyTimepointBrowser/studyTimepointStudy.js', 'client');
// Library functions
api.addFiles('client/lib/layoutManager.js', 'client');
api.addFiles('client/lib/createStacks.js', 'client');
api.addFiles('client/lib/getImageId.js', 'client');
api.addFiles('client/lib/getWADORSImageId.js', 'client');
api.addFiles('client/lib/metaDataProvider.js', 'client');
api.addFiles('client/lib/sortStudy.js', 'client');
api.addFiles('client/lib/toolManager.js', 'client');
api.addFiles('client/lib/enablePrefetchOnElement.js', 'client');
api.addFiles('client/lib/displayReferenceLines.js', 'client');
api.addFiles('client/lib/toggleDialog.js', 'client');
api.addFiles('client/lib/setActiveViewport.js', 'client');
api.addFiles('client/lib/switchToImageByIndex.js', 'client');
api.addFiles('client/lib/switchToImageRelative.js', 'client');
api.addFiles('client/lib/enableHotkeys.js', 'client');
api.addFiles('client/lib/viewportFunctions.js', 'client');
api.addFiles('client/lib/WLPresets.js', 'client');
api.addFiles('client/lib/resizeViewportElements.js', 'client');
api.addFiles('client/lib/setFocusToActiveViewport.js', 'client');
api.addFiles('client/lib/updateAllViewports.js', 'client');
api.addFiles('client/lib/stackImagePositionOffsetSynchronizer.js', 'client');
api.addFiles('client/lib/instanceClassSpecificViewport.js', 'client');
api.addFiles('client/lib/setMammogramViewportAlignment.js', 'client');
api.addFiles('client/lib/isImage.js', 'client');
api.addFiles('client/lib/sopClassDictionary.js', 'client');
api.addFiles('client/lib/debugReactivity.js', 'client');
api.addFiles('client/lib/unloadHandlers.js', 'client');
api.export('resizeViewportElements', 'client');
api.export('handleResize', 'client');
api.export('enableHotkeys', 'client');
api.export('enablePrefetchOnElement', 'client');
api.export('displayReferenceLines', 'client');
api.export('setActiveViewport', 'client');
api.export('createStacks', 'client');
api.export('getImageId', 'client');
api.export('getWADORSImageId', 'client');
api.export('metaDataProvider', 'client');
api.export('sortStudy', 'client');
api.export('updateOrientationMarkers', 'client');
api.export('setFocusToActiveViewport', 'client');
api.export('updateAllViewports', 'client');
api.export('queryStudies', 'client');
api.export('getNumberOfFilesInStudy', 'client');
api.export('importStudies', 'client');
api.export('getActiveViewportElement', 'client');
api.export('getInstanceClassDefaultViewport', 'client');
api.export('showConfirmDialog', 'client');
api.export('applyWLPreset', 'client');
api.export('toggleDialog', 'client');
api.export('isImage', 'client');
api.export('sopClassDictionary', 'client');
api.export('addMetaData', 'client');
api.export('hasMultipleFrames', 'client');
api.export('isStackScrollLinkingDisabled')
// Viewer management objects
api.export('toolManager', 'client');
api.export('LayoutManager', 'client');
// Collections
api.export('ViewerStudies', 'client');
// UI Helpers
api.addFiles([
'client/lib/helpers/formatDA.js',
'client/lib/helpers/formatJSDate.js',
'client/lib/helpers/jsDateFromNow.js',
'client/lib/helpers/formatNumberPrecision.js',
'client/lib/helpers/formatTM.js',
'client/lib/helpers/inc.js',
'client/lib/helpers/isDisplaySetActive.js',
'client/lib/helpers/getUsername.js',
'client/lib/helpers/capitalizeFirstLetter.js',
'client/lib/helpers/objectToPairs.js',
'client/lib/helpers/objectEach.js',
'client/lib/helpers/ifTypeIs.js',
'client/lib/helpers/prettyPrintStringify.js',
'client/lib/helpers/sorting.js',
'client/lib/helpers/studyThumbnails.js',
'client/lib/helpers/formatPN.js'
], 'client');
api.export('formatPN', 'client');
api.export('dialogPolyfill', 'client');
api.addFiles('client/lib/helpers/isTouchDevice.js', 'client');
api.export('isTouchDevice', 'client');
api.mainModule('main.js', 'client');
// TODO: Clean this up later, no real need to export these
// Need to move functionList into viewerbase
api.export('toggleCineDialog', 'client');
api.export('toggleCinePlay', 'client');
api.export('clearTools', 'client');
api.export('resetViewport', 'client');
api.export('invert', 'client');
api.export('flipV', 'client');
api.export('flipH', 'client');
api.export('rotateR', 'client');
api.export('rotateL', 'client');
api.export('link', 'client');
});