From 057735aab3f25c0ff0b061ec22b4a367374ea0ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Botelho=20Almeida?= Date: Fri, 17 Nov 2017 08:33:41 -0200 Subject: [PATCH] OHIF-184: Add menu tool option to download viewport as PNG or JPEG (#130) * OHIF-184: added download icon to the OHIF's tool bar * OHIF-184: refactor dialog toggle method to support cine dialog and download dialog * OHIF-184: Added hotKey support for downloadDialog. * OHIF-184: added disabled verify function * OHIF-184: Finished form structure. Created preview canvas. Added width and height config of the preview. Added the download feature. Added Cancel button. * OHIF-184: setting default extension as png * OHIF-184: moved download icon to more in toolbar * OHIF-184: enabling/disabling tools when toggling show annotations checkbox * OHIF-184: using a dynamic canvas to create the download content * OHIF-184: Download with configurable width and height. download distorced * OHIF-184: using dynamic elemento to manage download image instead of using the preview element * OHIF-184: make dialog dragable through preview * OHIF-184: removing useless line * OHIF-184: adding download icon to leasion tracker * OHIF-184: adding download dialog template to lesion tracker viewer --- .../toolbarSection/toolbarSection.js | 8 ++ .../client/components/viewer/viewer.html | 1 + .../toolbarSection/toolbarSection.js | 8 ++ .../client/components/viewer/viewer.html | 1 + .../viewer/downloadDialog/downloadDialog.html | 41 +++++++ .../viewer/downloadDialog/downloadDialog.js | 110 ++++++++++++++++++ .../viewer/downloadDialog/downloadDialog.styl | 42 +++++++ .../ohif-viewerbase/client/lib/hotkeyUtils.js | 6 + .../client/lib/viewportUtils.js | 33 +++++- Packages/ohif-viewerbase/package.js | 4 + 10 files changed, 250 insertions(+), 4 deletions(-) create mode 100644 Packages/ohif-viewerbase/client/components/viewer/downloadDialog/downloadDialog.html create mode 100644 Packages/ohif-viewerbase/client/components/viewer/downloadDialog/downloadDialog.js create mode 100644 Packages/ohif-viewerbase/client/components/viewer/downloadDialog/downloadDialog.styl diff --git a/LesionTracker/client/components/toolbarSection/toolbarSection.js b/LesionTracker/client/components/toolbarSection/toolbarSection.js index c5e3827b6..d6a5a05b5 100644 --- a/LesionTracker/client/components/toolbarSection/toolbarSection.js +++ b/LesionTracker/client/components/toolbarSection/toolbarSection.js @@ -124,6 +124,14 @@ Template.toolbarSection.helpers({ svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-elliptical-roi' }); + extraTools.push({ + id: 'toggleDownloadDialog', + title: 'Download', + classes: 'imageViewerCommand', + iconClasses: 'fa fa-camera', + active: () => $('#downloadDialog').is(':visible') + }); + extraTools.push({ id: 'toggleCineDialog', title: 'CINE', diff --git a/LesionTracker/client/components/viewer/viewer.html b/LesionTracker/client/components/viewer/viewer.html index 6c66556ef..b058d11c3 100644 --- a/LesionTracker/client/components/viewer/viewer.html +++ b/LesionTracker/client/components/viewer/viewer.html @@ -3,6 +3,7 @@ {{#if and Template.subscriptionsReady dataSourcesReady}} {{>confirmDeleteDialog}} {{>measurementTableHUD (clone this)}} + {{>downloadDialog}} {{>cineDialog}} {{/if}} diff --git a/OHIFViewer/client/components/toolbarSection/toolbarSection.js b/OHIFViewer/client/components/toolbarSection/toolbarSection.js index 113d9ac2b..204a18e30 100644 --- a/OHIFViewer/client/components/toolbarSection/toolbarSection.js +++ b/OHIFViewer/client/components/toolbarSection/toolbarSection.js @@ -104,6 +104,14 @@ Template.toolbarSection.helpers({ iconClasses: 'fa fa-square-o' }); + extraTools.push({ + id: 'toggleDownloadDialog', + title: 'Download', + classes: 'imageViewerCommand', + iconClasses: 'fa fa-camera', + active: () => $('#downloadDialog').is(':visible') + }); + extraTools.push({ id: 'invert', title: 'Invert', diff --git a/OHIFViewer/client/components/viewer/viewer.html b/OHIFViewer/client/components/viewer/viewer.html index 38c84ba1b..014a4632b 100644 --- a/OHIFViewer/client/components/viewer/viewer.html +++ b/OHIFViewer/client/components/viewer/viewer.html @@ -2,6 +2,7 @@ {{#if and Template.subscriptionsReady}}
{{>cineDialog}} + {{>downloadDialog}} {{>layoutChooser}} {{>annotationDialogs}} diff --git a/Packages/ohif-viewerbase/client/components/viewer/downloadDialog/downloadDialog.html b/Packages/ohif-viewerbase/client/components/viewer/downloadDialog/downloadDialog.html new file mode 100644 index 000000000..404c8bda1 --- /dev/null +++ b/Packages/ohif-viewerbase/client/components/viewer/downloadDialog/downloadDialog.html @@ -0,0 +1,41 @@ + \ No newline at end of file diff --git a/Packages/ohif-viewerbase/client/components/viewer/downloadDialog/downloadDialog.js b/Packages/ohif-viewerbase/client/components/viewer/downloadDialog/downloadDialog.js new file mode 100644 index 000000000..f0e45e021 --- /dev/null +++ b/Packages/ohif-viewerbase/client/components/viewer/downloadDialog/downloadDialog.js @@ -0,0 +1,110 @@ +import { Template } from 'meteor/templating'; +import { Session } from 'meteor/session'; +import { OHIF } from 'meteor/ohif:core'; +import { viewportUtils } from '../../../lib/viewportUtils'; + +function setElementSize(element, canvas, size, value) { + $(element)[size](value); + canvas[size] = value; + canvas.style[size] = `${value}px`; +} + +Template.downloadDialog.onCreated(() => { + const instance = Template.instance(); + + instance.autorun(() => { + Session.get('UpdateDownloadViewport'); + const activeViewport = viewportUtils.getActiveViewportElement(); + + if (activeViewport) { + const enabledElement = cornerstone.getEnabledElement(activeViewport); + + cornerstone.loadImage(enabledElement.image.imageId).then(function (image) { + cornerstone.displayImage(instance.$previewElement, image); + cornerstone.displayImage(instance.$downloadElement, image); + cornerstone.resize(instance.$previewElement, true); + + setElementSize(instance.$downloadElement, instance.$downloadCanvas, 'width', 300); + setElementSize(instance.$downloadElement, instance.$downloadCanvas, 'height', 200); + + cornerstone.fitToWindow(instance.$downloadElement); + }); + } + }) +}); + +Template.downloadDialog.onRendered(() => { + const instance = Template.instance(); + const $dialog = instance.$('#downloadDialog'); + + instance.$previewElement = $('#previewElement')[0]; + instance.$downloadElement = document.createElement('div'); + instance.availableTools = ['length', 'probe', 'simpleAngle', 'arrowAnnotate', 'ellipticalRoi', 'rectangleRoi']; + instance.showAnnotations = false; + + cornerstone.enable(instance.$previewElement); + cornerstone.enable(instance.$downloadElement); + instance.$downloadCanvas = $(instance.$downloadElement).find('canvas')[0]; + + // Make the dialog bounded and draggable + $dialog.draggable({ defaultElementCursor: 'move' }); + + // Polyfill for older browsers + dialogPolyfill.registerDialog($dialog.get(0)); + + // // Prevent dialog from being dragged when user clicks any button + const $controls = $dialog.find('.form-group, .instructions'); + $controls.on('mousedown touchstart', event => event.stopPropagation()); +}); + +Template.downloadDialog.events({ + 'change .form-group input[name=width]'(event, instance){ + const width = $(event.currentTarget).val(); + + setElementSize(instance.$downloadElement, instance.$downloadCanvas, 'width', width); + cornerstone.fitToWindow(instance.$downloadElement); + }, + + 'change .form-group input[name=height]'(event, instance){ + const height = $(event.currentTarget).val(); + + setElementSize(instance.$downloadElement, instance.$downloadCanvas, 'height', height); + cornerstone.fitToWindow(instance.$downloadElement); + }, + + 'change #downloadDialog .form-group .form-check input[type=checkbox]'(event, instance) { + const $previewElement = instance.$previewElement; + const $downloadElement = instance.$downloadElement; + + instance.showAnnotations = !instance.showAnnotations; + + const action = (instance.showAnnotations) ? 'enable' : 'disable'; + + instance.availableTools.forEach(tool => { + cornerstoneTools[tool][action]($previewElement); + cornerstoneTools[tool][action]($downloadElement); + }); + }, + + 'click .dropdown-menu .dropdown-item'(event, instance) { + const extension = $(event.currentTarget).text(); + const $extensionButton = $('.btn.extension'); + + $extensionButton.text(extension); + }, + + 'click button.download'(event, instance) { + const fileName = $('.fileName').val(); + const extension = $('.btn.extension').text().trim(); + + if (!fileName || !extension) { + return; + } + + cornerstoneTools.saveAs(instance.$downloadElement, `${fileName}.${extension}`, `image/${extension}`); + }, + + 'click button.cancel'(event, instance) { + viewportUtils.toggleDownloadDialog(); + } +}); \ No newline at end of file diff --git a/Packages/ohif-viewerbase/client/components/viewer/downloadDialog/downloadDialog.styl b/Packages/ohif-viewerbase/client/components/viewer/downloadDialog/downloadDialog.styl new file mode 100644 index 000000000..7d0f1ec2a --- /dev/null +++ b/Packages/ohif-viewerbase/client/components/viewer/downloadDialog/downloadDialog.styl @@ -0,0 +1,42 @@ +@import "{ohif:design}/app" + +#downloadDialog + theme('border', '2px solid $uiBorderColor', 0.95) + theme('background', '$uiGrayDarkest', 0.95) + theme('color', '$textSecondaryColor') + width: 626px + z-index: 1000 + + .instructions + margin-bottom: 20px + text-align: center + + .extension + color: #000000 + background-color: #888 + + .col-form-label + margin-top: 5px + + .dropdown-menu .dropdown-item + display: block; + width: 100%; + padding: 3px 1.5rem; + clear: both; + font-weight: 400; + color: #292b2c; + text-align: inherit; + white-space: nowrap; + background: 0 0; + border: 0; + + #previewElement, #downloadElement + width: 300px + height: 200px + margin: 0 auto + + #downloadElement + display: none + + .button-holder + margin-top: 15px \ No newline at end of file diff --git a/Packages/ohif-viewerbase/client/lib/hotkeyUtils.js b/Packages/ohif-viewerbase/client/lib/hotkeyUtils.js index b4d717df4..c4a28a061 100644 --- a/Packages/ohif-viewerbase/client/lib/hotkeyUtils.js +++ b/Packages/ohif-viewerbase/client/lib/hotkeyUtils.js @@ -62,6 +62,7 @@ Meteor.startup(function() { toggleOverlayTags: 'SHIFT', toggleCinePlay: 'SPACE', toggleCineDialog: '', + toggleDownloadDialog: '', // Preset hotkeys WLPreset0: '1', @@ -218,6 +219,11 @@ Meteor.startup(function() { name: 'Show/Hide Cine Controls', action: viewportUtils.toggleCineDialog, disabled: OHIF.viewerbase.viewportUtils.hasMultipleFrames + }, + toggleDownloadDialog: { + name: 'Show/Hide Download Dialog', + action: viewportUtils.toggleDownloadDialog, + disabled: () => !viewportUtils.isDownloadEnabled() } }, true); diff --git a/Packages/ohif-viewerbase/client/lib/viewportUtils.js b/Packages/ohif-viewerbase/client/lib/viewportUtils.js index 484708cb1..701dda8c3 100644 --- a/Packages/ohif-viewerbase/client/lib/viewportUtils.js +++ b/Packages/ohif-viewerbase/client/lib/viewportUtils.js @@ -172,11 +172,11 @@ const linkStackScroll = () => { // This function was originally defined alone inside client/lib/toggleDialog.js // and has been moved here to avoid circular dependency issues. -const toggleDialog = element => { +const toggleDialog = (element, closeAction) => { const $element = $(element); if($element.is('dialog')) { if (element.hasAttribute('open')) { - stopAllClips(); + if (closeAction) closeAction(); element.close(); } else { element.show(); @@ -187,8 +187,6 @@ const toggleDialog = element => { $element.toggleClass('dialog-closed', isClosed); $element.toggleClass('dialog-open', !isClosed); } - - Session.set('UpdateCINE', Math.random()); }; // Toggle the play/stop state for the cornerstone clip tool @@ -210,7 +208,24 @@ const toggleCinePlay = () => { // Show/hide the CINE dialog const toggleCineDialog = () => { const dialog = document.getElementById('cineDialog'); + + toggleDialog(dialog, stopAllClips); + Session.set('UpdateCINE', Random.id()); +}; + +const toggleDownloadDialog = () => { + const dialog = document.getElementById('downloadDialog'); + + stopActiveClip(); toggleDialog(dialog); + + Session.set('UpdateDownloadViewport', Random.id()); +}; + +const isDownloadEnabled = () => { + const activeViewport = getActiveViewportElement(); + + return activeViewport ? true : false; }; // Check if the clip is playing on the active viewport @@ -287,6 +302,14 @@ const stopAllClips = () => { }); }; +const stopActiveClip = () => { + const activeElement = getActiveViewportElement(); + + if ($(activeElement).find('canvas').length) { + cornerstoneTools.stopClip(activeElement); + } +} + const isStackScrollLinkingDisabled = () => { let linkableViewportsCount = 0; @@ -329,7 +352,9 @@ const viewportUtils = { toggleDialog, toggleCinePlay, toggleCineDialog, + toggleDownloadDialog, isPlaying, + isDownloadEnabled, hasMultipleFrames, stopAllClips, isStackScrollLinkingDisabled diff --git a/Packages/ohif-viewerbase/package.js b/Packages/ohif-viewerbase/package.js index a16929787..92fbb48cb 100644 --- a/Packages/ohif-viewerbase/package.js +++ b/Packages/ohif-viewerbase/package.js @@ -163,6 +163,10 @@ Package.onUse(function(api) { api.addFiles('client/components/viewer/cineDialog/cineDialog.js', 'client'); api.addFiles('client/components/viewer/cineDialog/cineDialog.styl', 'client'); + api.addFiles('client/components/viewer/downloadDialog/downloadDialog.html', 'client'); + api.addFiles('client/components/viewer/downloadDialog/downloadDialog.js', 'client'); + api.addFiles('client/components/viewer/downloadDialog/downloadDialog.styl', 'client'); + api.addFiles('client/components/viewer/toolbarSectionButton/toolbarSectionButton.html', 'client'); api.addFiles('client/components/viewer/toolbarSectionButton/toolbarSectionButton.js', 'client'); api.addFiles('client/components/viewer/toolbarSectionButton/toolbarSectionButton.styl', 'client');