diff --git a/extensions/cornerstone/src/CornerstoneViewportDownloadForm.js b/extensions/cornerstone/src/CornerstoneViewportDownloadForm.js new file mode 100644 index 000000000..7582c0960 --- /dev/null +++ b/extensions/cornerstone/src/CornerstoneViewportDownloadForm.js @@ -0,0 +1,148 @@ +import React from 'react'; +import cornerstone from 'cornerstone-core'; +import cornerstoneTools from 'cornerstone-tools'; +import PropTypes from 'prop-types'; + +import { ViewportDownloadForm } from '@ohif/ui'; +import { utils } from '@ohif/core'; + +import { getEnabledElement } from './state'; + +const MINIMUM_SIZE = 100; +const DEFAULT_SIZE = 512; +const MAX_TEXTURE_SIZE = 10000; + +const CornerstoneViewportDownloadForm = ({ onClose, activeViewportIndex }) => { + const activeEnabledElement = getEnabledElement(activeViewportIndex); + + const enableViewport = viewportElement => { + if (viewportElement) { + cornerstone.enable(viewportElement); + } + }; + + const disableViewport = viewportElement => { + if (viewportElement) { + cornerstone.disable(viewportElement); + } + }; + + const updateViewportPreview = (viewportElement, downloadCanvas, fileType) => + new Promise(resolve => { + cornerstone.fitToWindow(viewportElement); + + viewportElement.addEventListener( + 'cornerstoneimagerendered', + function updateViewport(event) { + const enabledElement = cornerstone.getEnabledElement(event.target) + .element; + const type = 'image/' + fileType; + const dataUrl = downloadCanvas.toDataURL(type, 1); + + let newWidth = enabledElement.offsetHeight; + let newHeight = enabledElement.offsetWidth; + + if (newWidth > DEFAULT_SIZE || newHeight > DEFAULT_SIZE) { + const multiplier = DEFAULT_SIZE / Math.max(newWidth, newHeight); + newHeight *= multiplier; + newWidth *= multiplier; + } + + resolve({ dataUrl, width: newWidth, height: newHeight }); + + viewportElement.removeEventListener( + 'cornerstoneimagerendered', + updateViewport + ); + } + ); + }); + + const loadImage = (activeViewport, viewportElement, width, height) => + new Promise(resolve => { + if (activeViewport && viewportElement) { + const enabledElement = cornerstone.getEnabledElement(activeViewport); + const viewport = Object.assign({}, enabledElement.viewport); + delete viewport.scale; + viewport.translation = { + x: 0, + y: 0, + }; + + cornerstone.loadImage(enabledElement.image.imageId).then(image => { + cornerstone.displayImage(viewportElement, image); + cornerstone.setViewport(viewportElement, viewport); + cornerstone.resize(viewportElement, true); + + const newWidth = Math.min(width || image.width, MAX_TEXTURE_SIZE); + const newHeight = Math.min(height || image.height, MAX_TEXTURE_SIZE); + + resolve({ image, width: newWidth, height: newHeight }); + }); + } + }); + + const toggleAnnotations = (toggle, viewportElement) => { + cornerstoneTools.store.state.tools.forEach(({ name }) => { + if (toggle) { + cornerstoneTools.setToolEnabledForElement(viewportElement, name); + } else { + cornerstoneTools.setToolDisabledForElement(viewportElement, name); + } + }); + }; + + const downloadBlob = ( + filename, + fileType, + viewportElement, + downloadCanvas + ) => { + const file = `${filename}.${fileType}`; + const mimetype = `image/${fileType}`; + + /* Handles JPEG images for IE11 */ + if (downloadCanvas.msToBlob && fileType === 'jpeg') { + const image = downloadCanvas.toDataURL(mimetype, 1); + const blob = utils.b64toBlob( + image.replace('data:image/jpeg;base64,', ''), + mimetype + ); + return window.navigator.msSaveBlob(blob, file); + } + + viewportElement.querySelector('canvas').toBlob(blob => { + const URLObj = window.URL || window.webkitURL; + const a = document.createElement('a'); + a.href = URLObj.createObjectURL(blob); + a.download = file; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + }); + }; + + return ( + + ); +}; + +CornerstoneViewportDownloadForm.propTypes = { + onClose: PropTypes.func, + activeViewportIndex: PropTypes.number.isRequired, +}; + +export default CornerstoneViewportDownloadForm; diff --git a/extensions/cornerstone/src/commandsModule.js b/extensions/cornerstone/src/commandsModule.js index 5b71edf1a..2977c6247 100644 --- a/extensions/cornerstone/src/commandsModule.js +++ b/extensions/cornerstone/src/commandsModule.js @@ -1,233 +1,260 @@ import cornerstone from 'cornerstone-core'; import cornerstoneTools from 'cornerstone-tools'; import OHIF from '@ohif/core'; + import { getEnabledElement } from './state'; +import CornerstoneViewportDownloadForm from './CornerstoneViewportDownloadForm'; const scroll = cornerstoneTools.import('util/scroll'); -const actions = { - rotateViewport: ({ viewports, rotation }) => { - const enabledElement = getEnabledElement(viewports.activeViewportIndex); +const commandsModule = ({ servicesManager }) => { + const actions = { + rotateViewport: ({ viewports, rotation }) => { + const enabledElement = getEnabledElement(viewports.activeViewportIndex); - if (enabledElement) { - let viewport = cornerstone.getViewport(enabledElement); - viewport.rotation += rotation; - cornerstone.setViewport(enabledElement, viewport); - } - }, - flipViewportHorizontal: ({ viewports }) => { - const enabledElement = getEnabledElement(viewports.activeViewportIndex); - - if (enabledElement) { - let viewport = cornerstone.getViewport(enabledElement); - viewport.hflip = !viewport.hflip; - cornerstone.setViewport(enabledElement, viewport); - } - }, - flipViewportVertical: ({ viewports }) => { - const enabledElement = getEnabledElement(viewports.activeViewportIndex); - - if (enabledElement) { - let viewport = cornerstone.getViewport(enabledElement); - viewport.vflip = !viewport.vflip; - cornerstone.setViewport(enabledElement, viewport); - } - }, - scaleViewport: ({ direction, viewports }) => { - const enabledElement = getEnabledElement(viewports.activeViewportIndex); - const step = direction * 0.15; - - if (enabledElement) { - if (step) { + if (enabledElement) { let viewport = cornerstone.getViewport(enabledElement); - viewport.scale += step; + viewport.rotation += rotation; cornerstone.setViewport(enabledElement, viewport); - } else { - cornerstone.fitToWindow(enabledElement); } - } - }, - resetViewport: ({ viewports }) => { - const enabledElement = getEnabledElement(viewports.activeViewportIndex); + }, + flipViewportHorizontal: ({ viewports }) => { + const enabledElement = getEnabledElement(viewports.activeViewportIndex); - if (enabledElement) { - cornerstone.reset(enabledElement); - } - }, - invertViewport: ({ viewports }) => { - const enabledElement = getEnabledElement(viewports.activeViewportIndex); + if (enabledElement) { + let viewport = cornerstone.getViewport(enabledElement); + viewport.hflip = !viewport.hflip; + cornerstone.setViewport(enabledElement, viewport); + } + }, + flipViewportVertical: ({ viewports }) => { + const enabledElement = getEnabledElement(viewports.activeViewportIndex); - if (enabledElement) { - let viewport = cornerstone.getViewport(enabledElement); - viewport.invert = !viewport.invert; - cornerstone.setViewport(enabledElement, viewport); - } - }, - // TODO: this is receiving `evt` from `ToolbarRow`. We could use it to have - // better mouseButtonMask sets. - setToolActive: ({ toolName }) => { - if (!toolName) { - console.warn('No toolname provided to setToolActive command'); - } - cornerstoneTools.setToolActive(toolName, { mouseButtonMask: 1 }); - }, - updateViewportDisplaySet: ({ direction }) => { - // TODO - console.warn('updateDisplaySet: ', direction); - }, - clearAnnotations: ({ viewports }) => { - const element = getEnabledElement(viewports.activeViewportIndex); - if (!element) { - return; - } + if (enabledElement) { + let viewport = cornerstone.getViewport(enabledElement); + viewport.vflip = !viewport.vflip; + cornerstone.setViewport(enabledElement, viewport); + } + }, + scaleViewport: ({ direction, viewports }) => { + const enabledElement = getEnabledElement(viewports.activeViewportIndex); + const step = direction * 0.15; - const enabledElement = cornerstone.getEnabledElement(element); - if (!enabledElement || !enabledElement.image) { - return; - } - - const { - toolState, - } = cornerstoneTools.globalImageIdSpecificToolStateManager; - if ( - !toolState || - toolState.hasOwnProperty(enabledElement.image.imageId) === false - ) { - return; - } - - const imageIdToolState = toolState[enabledElement.image.imageId]; - - const measurementsToRemove = []; - - Object.keys(imageIdToolState).forEach(toolType => { - const { data } = imageIdToolState[toolType]; - - data.forEach(measurementData => { - const { _id, lesionNamingNumber, measurementNumber } = measurementData; - if (!_id) { - return; + if (enabledElement) { + if (step) { + let viewport = cornerstone.getViewport(enabledElement); + viewport.scale += step; + cornerstone.setViewport(enabledElement, viewport); + } else { + cornerstone.fitToWindow(enabledElement); } + } + }, + resetViewport: ({ viewports }) => { + const enabledElement = getEnabledElement(viewports.activeViewportIndex); - measurementsToRemove.push({ - toolType, - _id, - lesionNamingNumber, - measurementNumber, + if (enabledElement) { + cornerstone.reset(enabledElement); + } + }, + invertViewport: ({ viewports }) => { + const enabledElement = getEnabledElement(viewports.activeViewportIndex); + + if (enabledElement) { + let viewport = cornerstone.getViewport(enabledElement); + viewport.invert = !viewport.invert; + cornerstone.setViewport(enabledElement, viewport); + } + }, + // TODO: this is receiving `evt` from `ToolbarRow`. We could use it to have + // better mouseButtonMask sets. + setToolActive: ({ toolName }) => { + if (!toolName) { + console.warn('No toolname provided to setToolActive command'); + } + cornerstoneTools.setToolActive(toolName, { mouseButtonMask: 1 }); + }, + updateViewportDisplaySet: ({ direction }) => { + // TODO + console.warn('updateDisplaySet: ', direction); + }, + clearAnnotations: ({ viewports }) => { + const element = getEnabledElement(viewports.activeViewportIndex); + if (!element) { + return; + } + + const enabledElement = cornerstone.getEnabledElement(element); + if (!enabledElement || !enabledElement.image) { + return; + } + + const { + toolState, + } = cornerstoneTools.globalImageIdSpecificToolStateManager; + if ( + !toolState || + toolState.hasOwnProperty(enabledElement.image.imageId) === false + ) { + return; + } + + const imageIdToolState = toolState[enabledElement.image.imageId]; + + const measurementsToRemove = []; + + Object.keys(imageIdToolState).forEach(toolType => { + const { data } = imageIdToolState[toolType]; + + data.forEach(measurementData => { + const { + _id, + lesionNamingNumber, + measurementNumber, + } = measurementData; + if (!_id) { + return; + } + + measurementsToRemove.push({ + toolType, + _id, + lesionNamingNumber, + measurementNumber, + }); }); }); - }); - measurementsToRemove.forEach(measurementData => { - OHIF.measurements.MeasurementHandlers.onRemoved({ - detail: { - toolType: measurementData.toolType, - measurementData, + measurementsToRemove.forEach(measurementData => { + OHIF.measurements.MeasurementHandlers.onRemoved({ + detail: { + toolType: measurementData.toolType, + measurementData, + }, + }); + }); + }, + nextImage: ({ viewports }) => { + const enabledElement = getEnabledElement(viewports.activeViewportIndex); + scroll(enabledElement, 1); + }, + previousImage: ({ viewports }) => { + const enabledElement = getEnabledElement(viewports.activeViewportIndex); + scroll(enabledElement, -1); + }, + getActiveViewportEnabledElement: ({ viewports }) => { + const enabledElement = getEnabledElement(viewports.activeViewportIndex); + return enabledElement; + }, + showDownloadViewportModal: ({ title, viewports }) => { + const activeViewportIndex = viewports.activeViewportIndex; + const { UIModalService } = servicesManager.services; + UIModalService.show({ + content: CornerstoneViewportDownloadForm, + title, + contentProps: { + activeViewportIndex, + onClose: UIModalService.hide, }, }); - }); - }, - nextImage: ({ viewports }) => { - const enabledElement = getEnabledElement(viewports.activeViewportIndex); - scroll(enabledElement, 1); - }, - previousImage: ({ viewports }) => { - const enabledElement = getEnabledElement(viewports.activeViewportIndex); - scroll(enabledElement, -1); - }, - getActiveViewportEnabledElement: ({ viewports }) => { - const enabledElement = getEnabledElement(viewports.activeViewportIndex); - return enabledElement; - }, + }, + }; + + const definitions = { + showDownloadViewportModal: { + commandFn: actions.showDownloadViewportModal, + storeContexts: ['viewports'], + options: {}, + }, + getActiveViewportEnabledElement: { + commandFn: actions.getActiveViewportEnabledElement, + storeContexts: ['viewports'], + options: {}, + }, + rotateViewportCW: { + commandFn: actions.rotateViewport, + storeContexts: ['viewports'], + options: { rotation: 90 }, + }, + rotateViewportCCW: { + commandFn: actions.rotateViewport, + storeContexts: ['viewports'], + options: { rotation: -90 }, + }, + invertViewport: { + commandFn: actions.invertViewport, + storeContexts: ['viewports'], + options: {}, + }, + flipViewportVertical: { + commandFn: actions.flipViewportVertical, + storeContexts: ['viewports'], + options: {}, + }, + flipViewportHorizontal: { + commandFn: actions.flipViewportHorizontal, + storeContexts: ['viewports'], + options: {}, + }, + scaleUpViewport: { + commandFn: actions.scaleViewport, + storeContexts: ['viewports'], + options: { direction: 1 }, + }, + scaleDownViewport: { + commandFn: actions.scaleViewport, + storeContexts: ['viewports'], + options: { direction: -1 }, + }, + fitViewportToWindow: { + commandFn: actions.scaleViewport, + storeContexts: ['viewports'], + options: { direction: 0 }, + }, + resetViewport: { + commandFn: actions.resetViewport, + storeContexts: ['viewports'], + options: {}, + }, + clearAnnotations: { + commandFn: actions.clearAnnotations, + storeContexts: ['viewports'], + options: {}, + }, + nextImage: { + commandFn: actions.nextImage, + storeContexts: ['viewports'], + options: {}, + }, + previousImage: { + commandFn: actions.previousImage, + storeContexts: ['viewports'], + options: {}, + }, + // TODO: First/Last image + // Next/Previous series/DisplaySet + nextViewportDisplaySet: { + commandFn: actions.updateViewportDisplaySet, + storeContexts: [], + options: { direction: 1 }, + }, + previousViewportDisplaySet: { + commandFn: actions.updateViewportDisplaySet, + storeContexts: [], + options: { direction: -1 }, + }, + // TOOLS + setToolActive: { + commandFn: actions.setToolActive, + storeContexts: [], + options: {}, + }, + }; + + return { + actions, + definitions, + defaultContext: 'ACTIVE_VIEWPORT::CORNERSTONE', + }; }; -const definitions = { - getActiveViewportEnabledElement: { - commandFn: actions.getActiveViewportEnabledElement, - storeContexts: ['viewports'], - options: {}, - }, - rotateViewportCW: { - commandFn: actions.rotateViewport, - storeContexts: ['viewports'], - options: { rotation: 90 }, - }, - rotateViewportCCW: { - commandFn: actions.rotateViewport, - storeContexts: ['viewports'], - options: { rotation: -90 }, - }, - invertViewport: { - commandFn: actions.invertViewport, - storeContexts: ['viewports'], - options: {}, - }, - flipViewportVertical: { - commandFn: actions.flipViewportVertical, - storeContexts: ['viewports'], - options: {}, - }, - flipViewportHorizontal: { - commandFn: actions.flipViewportHorizontal, - storeContexts: ['viewports'], - options: {}, - }, - scaleUpViewport: { - commandFn: actions.scaleViewport, - storeContexts: ['viewports'], - options: { direction: 1 }, - }, - scaleDownViewport: { - commandFn: actions.scaleViewport, - storeContexts: ['viewports'], - options: { direction: -1 }, - }, - fitViewportToWindow: { - commandFn: actions.scaleViewport, - storeContexts: ['viewports'], - options: { direction: 0 }, - }, - resetViewport: { - commandFn: actions.resetViewport, - storeContexts: ['viewports'], - options: {}, - }, - clearAnnotations: { - commandFn: actions.clearAnnotations, - storeContexts: ['viewports'], - options: {}, - }, - nextImage: { - commandFn: actions.nextImage, - storeContexts: ['viewports'], - options: {}, - }, - previousImage: { - commandFn: actions.previousImage, - storeContexts: ['viewports'], - options: {}, - }, - // TODO: First/Last image - // Next/Previous series/DisplaySet - nextViewportDisplaySet: { - commandFn: actions.updateViewportDisplaySet, - storeContexts: [], - options: { direction: 1 }, - }, - previousViewportDisplaySet: { - commandFn: actions.updateViewportDisplaySet, - storeContexts: [], - options: { direction: -1 }, - }, - // TOOLS - setToolActive: { - commandFn: actions.setToolActive, - storeContexts: [], - options: {}, - }, -}; - -export default { - actions, - definitions, - defaultContext: 'ACTIVE_VIEWPORT::CORNERSTONE', -}; +export default commandsModule; diff --git a/extensions/cornerstone/src/index.js b/extensions/cornerstone/src/index.js index 6470382c9..104ba4f51 100644 --- a/extensions/cornerstone/src/index.js +++ b/extensions/cornerstone/src/index.js @@ -2,6 +2,7 @@ import init from './init.js'; import asyncComponent from './asyncComponent.js'; import commandsModule from './commandsModule.js'; import toolbarModule from './toolbarModule.js'; +import CornerstoneViewportDownloadForm from './CornerstoneViewportDownloadForm'; const OHIFCornerstoneViewport = asyncComponent(() => import( @@ -33,7 +34,9 @@ export default { getToolbarModule() { return toolbarModule; }, - getCommandsModule() { - return commandsModule; + getCommandsModule({ servicesManager }) { + return commandsModule({ servicesManager }); }, }; + +export { CornerstoneViewportDownloadForm }; diff --git a/platform/viewer/src/connectedComponents/ConnectedViewportDownloadForm.js b/platform/viewer/src/connectedComponents/ConnectedViewportDownloadForm.js deleted file mode 100644 index bebab6b8c..000000000 --- a/platform/viewer/src/connectedComponents/ConnectedViewportDownloadForm.js +++ /dev/null @@ -1,131 +0,0 @@ -import { connect } from 'react-redux'; -import { ViewportDownloadForm } from '@ohif/ui'; -import { utils } from '@ohif/core'; -import cornerstone from 'cornerstone-core'; -import cornerstoneTools from 'cornerstone-tools'; -import { commandsManager } from './../App.js'; - -const MINIMUM_SIZE = 100; -const DEFAULT_SIZE = 512; -const MAX_TEXTURE_SIZE = 10000; - -const mapStateToProps = (state, ownProps) => { - const activeEnabledElement = commandsManager.runCommand( - 'getActiveViewportEnabledElement' - ); - - return { - onClose: ownProps.hide, - minimumSize: MINIMUM_SIZE, - maximumSize: MAX_TEXTURE_SIZE, - defaultSize: DEFAULT_SIZE, - canvasClass: 'cornerstone-canvas', - activeViewport: activeEnabledElement, - enableViewport: viewportElement => { - if (viewportElement) { - cornerstone.enable(viewportElement); - } - }, - disableViewport: viewportElement => { - if (viewportElement) { - cornerstone.disable(viewportElement); - } - }, - updateViewportPreview: (viewportElement, downloadCanvas, fileType) => - new Promise(resolve => { - cornerstone.fitToWindow(viewportElement); - - viewportElement.addEventListener( - 'cornerstoneimagerendered', - function updateViewport(event) { - const enabledElement = cornerstone.getEnabledElement(event.target) - .element; - const type = 'image/' + fileType; - const dataUrl = downloadCanvas.toDataURL(type, 1); - - let newWidth = enabledElement.offsetHeight; - let newHeight = enabledElement.offsetWidth; - - if (newWidth > DEFAULT_SIZE || newHeight > DEFAULT_SIZE) { - const multiplier = DEFAULT_SIZE / Math.max(newWidth, newHeight); - newHeight *= multiplier; - newWidth *= multiplier; - } - - resolve({ dataUrl, width: newWidth, height: newHeight }); - - viewportElement.removeEventListener( - 'cornerstoneimagerendered', - updateViewport - ); - } - ); - }), - loadImage: (activeViewport, viewportElement, width, height) => - new Promise(resolve => { - if (activeViewport && viewportElement) { - const enabledElement = cornerstone.getEnabledElement(activeViewport); - const viewport = Object.assign({}, enabledElement.viewport); - delete viewport.scale; - viewport.translation = { - x: 0, - y: 0, - }; - - cornerstone.loadImage(enabledElement.image.imageId).then(image => { - cornerstone.displayImage(viewportElement, image); - cornerstone.setViewport(viewportElement, viewport); - cornerstone.resize(viewportElement, true); - - const newWidth = Math.min(width || image.width, MAX_TEXTURE_SIZE); - const newHeight = Math.min( - height || image.height, - MAX_TEXTURE_SIZE - ); - - resolve({ image, width: newWidth, height: newHeight }); - }); - } - }), - toggleAnnotations: (toggle, viewportElement) => { - cornerstoneTools.store.state.tools.forEach(({ name }) => { - if (toggle) { - cornerstoneTools.setToolEnabledForElement(viewportElement, name); - } else { - cornerstoneTools.setToolDisabledForElement(viewportElement, name); - } - }); - }, - downloadBlob: (filename, fileType, viewportElement, downloadCanvas) => { - const file = `${filename}.${fileType}`; - const mimetype = `image/${fileType}`; - - /* Handles JPEG images for IE11 */ - if (downloadCanvas.msToBlob && fileType === 'jpeg') { - const image = downloadCanvas.toDataURL(mimetype, 1); - const blob = utils.b64toBlob( - image.replace('data:image/jpeg;base64,', ''), - mimetype - ); - return window.navigator.msSaveBlob(blob, file); - } - - viewportElement.querySelector('canvas').toBlob(blob => { - const URLObj = window.URL || window.webkitURL; - const a = document.createElement('a'); - a.href = URLObj.createObjectURL(blob); - a.download = file; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - }); - }, - }; -}; - -const ConnectedViewportDownloadForm = connect( - mapStateToProps, - null -)(ViewportDownloadForm); - -export default ConnectedViewportDownloadForm; diff --git a/platform/viewer/src/connectedComponents/ToolbarRow.js b/platform/viewer/src/connectedComponents/ToolbarRow.js index 3b7c53755..4957d7148 100644 --- a/platform/viewer/src/connectedComponents/ToolbarRow.js +++ b/platform/viewer/src/connectedComponents/ToolbarRow.js @@ -1,7 +1,8 @@ import React, { Component } from 'react'; -import { MODULE_TYPES } from '@ohif/core'; import PropTypes from 'prop-types'; import { withTranslation } from 'react-i18next'; + +import { MODULE_TYPES } from '@ohif/core'; import { ExpandableToolMenu, RoundedButtonGroup, @@ -14,7 +15,6 @@ import './ToolbarRow.css'; import { commandsManager, extensionManager } from './../App.js'; import ConnectedCineDialog from './ConnectedCineDialog'; -import ConnectedViewportDownloadForm from './ConnectedViewportDownloadForm'; import ConnectedLayoutButton from './ConnectedLayoutButton'; import ConnectedPluginSwitch from './ConnectedPluginSwitch.js'; @@ -308,8 +308,7 @@ function _handleBuiltIn(button) { } if (options.behavior === 'DOWNLOAD_SCREEN_SHOT') { - modal.show({ - content: ConnectedViewportDownloadForm, + commandsManager.runCommand('showDownloadViewportModal', { title: t('Download High Quality Image'), }); }