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
This commit is contained in:
parent
e6b18f6c96
commit
057735aab3
@ -124,6 +124,14 @@ Template.toolbarSection.helpers({
|
|||||||
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-elliptical-roi'
|
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({
|
extraTools.push({
|
||||||
id: 'toggleCineDialog',
|
id: 'toggleCineDialog',
|
||||||
title: 'CINE',
|
title: 'CINE',
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
{{#if and Template.subscriptionsReady dataSourcesReady}}
|
{{#if and Template.subscriptionsReady dataSourcesReady}}
|
||||||
{{>confirmDeleteDialog}}
|
{{>confirmDeleteDialog}}
|
||||||
{{>measurementTableHUD (clone this)}}
|
{{>measurementTableHUD (clone this)}}
|
||||||
|
{{>downloadDialog}}
|
||||||
{{>cineDialog}}
|
{{>cineDialog}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -104,6 +104,14 @@ Template.toolbarSection.helpers({
|
|||||||
iconClasses: 'fa fa-square-o'
|
iconClasses: 'fa fa-square-o'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
extraTools.push({
|
||||||
|
id: 'toggleDownloadDialog',
|
||||||
|
title: 'Download',
|
||||||
|
classes: 'imageViewerCommand',
|
||||||
|
iconClasses: 'fa fa-camera',
|
||||||
|
active: () => $('#downloadDialog').is(':visible')
|
||||||
|
});
|
||||||
|
|
||||||
extraTools.push({
|
extraTools.push({
|
||||||
id: 'invert',
|
id: 'invert',
|
||||||
title: 'Invert',
|
title: 'Invert',
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
{{#if and Template.subscriptionsReady}}
|
{{#if and Template.subscriptionsReady}}
|
||||||
<div class="viewerDialogs">
|
<div class="viewerDialogs">
|
||||||
{{>cineDialog}}
|
{{>cineDialog}}
|
||||||
|
{{>downloadDialog}}
|
||||||
{{>layoutChooser}}
|
{{>layoutChooser}}
|
||||||
{{>annotationDialogs}}
|
{{>annotationDialogs}}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,41 @@
|
|||||||
|
<template name="downloadDialog">
|
||||||
|
<dialog id="downloadDialog">
|
||||||
|
{{#form id='downloadDialogForm' schema=instance.schema api=instance.api}}
|
||||||
|
<div class="instructions">Please specify the dimensions, filename, and desired type for the output image.</div>
|
||||||
|
<div class="form-group row">
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" id="fileName" class="form-control fileName" aria-label="File name input">
|
||||||
|
<div class="input-group-btn">
|
||||||
|
<button type="button" class="btn btn-secondary dropdown-toggle extension" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">png</button>
|
||||||
|
<div class="dropdown-menu dropdown-menu-right">
|
||||||
|
<a class="dropdown-item" href="#">png</a>
|
||||||
|
<a class="dropdown-item" href="#">jpeg</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-check col-sm-4">
|
||||||
|
<label class="form-check-label">
|
||||||
|
<input type="checkbox" class="form-check-input"> Show Annotations
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label for="width" class="col-sm-2 col-form-label">Width (px):</label>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<input type="number" name="width" id="width" class="form-control" value="300"/>
|
||||||
|
</div>
|
||||||
|
<label for="height" class="col-sm-2 col-form-label">Height (px):</label>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<input type="number" name="height" id="height" class="form-control" value="200"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="previewElement"></div>
|
||||||
|
<div class="form-group button-holder row">
|
||||||
|
<button class="btn btn-danger col-sm-offset-2 col-sm-3 cancel">cancel</button>
|
||||||
|
<button class="btn btn-primary col-sm-offset-2 col-sm-3 download">download</button>
|
||||||
|
</div>
|
||||||
|
{{/form}}
|
||||||
|
</dialog>
|
||||||
|
</template>
|
||||||
@ -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();
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -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
|
||||||
@ -62,6 +62,7 @@ Meteor.startup(function() {
|
|||||||
toggleOverlayTags: 'SHIFT',
|
toggleOverlayTags: 'SHIFT',
|
||||||
toggleCinePlay: 'SPACE',
|
toggleCinePlay: 'SPACE',
|
||||||
toggleCineDialog: '',
|
toggleCineDialog: '',
|
||||||
|
toggleDownloadDialog: '',
|
||||||
|
|
||||||
// Preset hotkeys
|
// Preset hotkeys
|
||||||
WLPreset0: '1',
|
WLPreset0: '1',
|
||||||
@ -218,6 +219,11 @@ Meteor.startup(function() {
|
|||||||
name: 'Show/Hide Cine Controls',
|
name: 'Show/Hide Cine Controls',
|
||||||
action: viewportUtils.toggleCineDialog,
|
action: viewportUtils.toggleCineDialog,
|
||||||
disabled: OHIF.viewerbase.viewportUtils.hasMultipleFrames
|
disabled: OHIF.viewerbase.viewportUtils.hasMultipleFrames
|
||||||
|
},
|
||||||
|
toggleDownloadDialog: {
|
||||||
|
name: 'Show/Hide Download Dialog',
|
||||||
|
action: viewportUtils.toggleDownloadDialog,
|
||||||
|
disabled: () => !viewportUtils.isDownloadEnabled()
|
||||||
}
|
}
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
|
|||||||
@ -172,11 +172,11 @@ const linkStackScroll = () => {
|
|||||||
|
|
||||||
// This function was originally defined alone inside client/lib/toggleDialog.js
|
// This function was originally defined alone inside client/lib/toggleDialog.js
|
||||||
// and has been moved here to avoid circular dependency issues.
|
// and has been moved here to avoid circular dependency issues.
|
||||||
const toggleDialog = element => {
|
const toggleDialog = (element, closeAction) => {
|
||||||
const $element = $(element);
|
const $element = $(element);
|
||||||
if($element.is('dialog')) {
|
if($element.is('dialog')) {
|
||||||
if (element.hasAttribute('open')) {
|
if (element.hasAttribute('open')) {
|
||||||
stopAllClips();
|
if (closeAction) closeAction();
|
||||||
element.close();
|
element.close();
|
||||||
} else {
|
} else {
|
||||||
element.show();
|
element.show();
|
||||||
@ -187,8 +187,6 @@ const toggleDialog = element => {
|
|||||||
$element.toggleClass('dialog-closed', isClosed);
|
$element.toggleClass('dialog-closed', isClosed);
|
||||||
$element.toggleClass('dialog-open', !isClosed);
|
$element.toggleClass('dialog-open', !isClosed);
|
||||||
}
|
}
|
||||||
|
|
||||||
Session.set('UpdateCINE', Math.random());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Toggle the play/stop state for the cornerstone clip tool
|
// Toggle the play/stop state for the cornerstone clip tool
|
||||||
@ -210,7 +208,24 @@ const toggleCinePlay = () => {
|
|||||||
// Show/hide the CINE dialog
|
// Show/hide the CINE dialog
|
||||||
const toggleCineDialog = () => {
|
const toggleCineDialog = () => {
|
||||||
const dialog = document.getElementById('cineDialog');
|
const dialog = document.getElementById('cineDialog');
|
||||||
|
|
||||||
|
toggleDialog(dialog, stopAllClips);
|
||||||
|
Session.set('UpdateCINE', Random.id());
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleDownloadDialog = () => {
|
||||||
|
const dialog = document.getElementById('downloadDialog');
|
||||||
|
|
||||||
|
stopActiveClip();
|
||||||
toggleDialog(dialog);
|
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
|
// 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 = () => {
|
const isStackScrollLinkingDisabled = () => {
|
||||||
let linkableViewportsCount = 0;
|
let linkableViewportsCount = 0;
|
||||||
@ -329,7 +352,9 @@ const viewportUtils = {
|
|||||||
toggleDialog,
|
toggleDialog,
|
||||||
toggleCinePlay,
|
toggleCinePlay,
|
||||||
toggleCineDialog,
|
toggleCineDialog,
|
||||||
|
toggleDownloadDialog,
|
||||||
isPlaying,
|
isPlaying,
|
||||||
|
isDownloadEnabled,
|
||||||
hasMultipleFrames,
|
hasMultipleFrames,
|
||||||
stopAllClips,
|
stopAllClips,
|
||||||
isStackScrollLinkingDisabled
|
isStackScrollLinkingDisabled
|
||||||
|
|||||||
@ -163,6 +163,10 @@ Package.onUse(function(api) {
|
|||||||
api.addFiles('client/components/viewer/cineDialog/cineDialog.js', 'client');
|
api.addFiles('client/components/viewer/cineDialog/cineDialog.js', 'client');
|
||||||
api.addFiles('client/components/viewer/cineDialog/cineDialog.styl', '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.html', 'client');
|
||||||
api.addFiles('client/components/viewer/toolbarSectionButton/toolbarSectionButton.js', 'client');
|
api.addFiles('client/components/viewer/toolbarSectionButton/toolbarSectionButton.js', 'client');
|
||||||
api.addFiles('client/components/viewer/toolbarSectionButton/toolbarSectionButton.styl', 'client');
|
api.addFiles('client/components/viewer/toolbarSectionButton/toolbarSectionButton.styl', 'client');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user