diff --git a/package-lock.json b/package-lock.json index ff6d0b3f..4ea8db4a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "BSD-3-Clause", "dependencies": { "@kitware/vtk.js": "^26.9.15", - "@material/web": "1.0.1", + "@material/web": "^1.0.1", "@thewtex/iconselect.js": "^2.1.2", "@xstate/inspect": "^0.4.1", "axios": "^1.6.0", diff --git a/src/Rendering/Images/createImagesRenderingMachine.js b/src/Rendering/Images/createImagesRenderingMachine.js index bc4e52aa..7d661135 100644 --- a/src/Rendering/Images/createImagesRenderingMachine.js +++ b/src/Rendering/Images/createImagesRenderingMachine.js @@ -148,6 +148,9 @@ function createImagesRenderingMachine(options, context) { to: (c, e) => `imageRenderingActor-${e.data}`, }), }, + DOWNLOAD_IMAGE: { + actions: ['downloadImage'], + }, ...makeTransitions( [ 'IMAGE_GRADIENT_OPACITY_CHANGED', diff --git a/src/Rendering/VTKJS/Images/imagesRenderingMachineOptions.js b/src/Rendering/VTKJS/Images/imagesRenderingMachineOptions.js index 566f89c0..b80a63aa 100644 --- a/src/Rendering/VTKJS/Images/imagesRenderingMachineOptions.js +++ b/src/Rendering/VTKJS/Images/imagesRenderingMachineOptions.js @@ -1,3 +1,5 @@ +import { convertVtkToItkImage } from 'vtk.js/Sources/Common/DataModel/ITKHelper' +import { writeImageArrayBuffer, copyImage } from 'itk-wasm' import createImageRenderer from './createImageRenderer' import toggleLayerVisibility from './toggleLayerVisibility' import toggleLayerBBox from './toggleLayerBBox' @@ -64,6 +66,23 @@ const isTargetScaleLoaded = context => { return loadedScale === targetScale } +function downloadArray(content, filename = 'download') { + const url = URL.createObjectURL(new Blob([content])) + const a = document.createElement('a') + a.href = url + a.download = filename + document.body.appendChild(a) + function clickHandler() { + setTimeout(() => { + URL.revokeObjectURL(url) + a.removeEventListener('click', clickHandler) + }, 200) + } + a.addEventListener('click', clickHandler, false) + a.click() + return a +} + const imagesRenderingMachineOptions = { imageRenderingActor: { services: { @@ -117,6 +136,21 @@ const imagesRenderingMachineOptions = { actions: { selectImageLayer, + downloadImage: (context, event) => { + const { name, format } = event.data + const fileName = `${name}.${format}` + const actorContext = context.images.actorContext.get(name) + const fusedImage = actorContext.fusedImage + if (!fusedImage) { + console.warn('No image to download') + } + const itkImage = copyImage(convertVtkToItkImage(fusedImage, true)) + writeImageArrayBuffer(null, itkImage, fileName).then( + ({ arrayBuffer }) => { + downloadArray(arrayBuffer, fileName) + } + ) + }, }, } diff --git a/src/Rendering/createRenderingMachine.js b/src/Rendering/createRenderingMachine.js index 27a05e1d..5d4987ff 100644 --- a/src/Rendering/createRenderingMachine.js +++ b/src/Rendering/createRenderingMachine.js @@ -266,6 +266,9 @@ const createRenderingMachine = (options, context) => { TOGGLE_LAYER_BBOX: { actions: forwardTo('images'), }, + DOWNLOAD_IMAGE: { + actions: forwardTo('images'), + }, }, }, }, diff --git a/src/UI/reference-ui/dist/referenceUIMachineOptions.js b/src/UI/reference-ui/dist/referenceUIMachineOptions.js index ea5ff84d..e88f0f8d 100644 --- a/src/UI/reference-ui/dist/referenceUIMachineOptions.js +++ b/src/UI/reference-ui/dist/referenceUIMachineOptions.js @@ -10895,11 +10895,12 @@ function createLayerEntry(context, name, layer) { ) .concat( extensions - .map(function(extension) { + .map(function(extension, i) { return '') }) diff --git a/src/UI/reference-ui/src/Layers/createLayerInterface.js b/src/UI/reference-ui/src/Layers/createLayerInterface.js index 2aef5a6f..ddb7a37e 100644 --- a/src/UI/reference-ui/src/Layers/createLayerInterface.js +++ b/src/UI/reference-ui/src/Layers/createLayerInterface.js @@ -109,9 +109,11 @@ function createLayerEntry(context, name, layer) {
${extensions .map( - extension => + (extension, i) => `` ) diff --git a/src/createViewer.js b/src/createViewer.js index cde21321..9071b499 100644 --- a/src/createViewer.js +++ b/src/createViewer.js @@ -183,9 +183,6 @@ const createViewer = async ( break case 'TAKE_SCREENSHOT': break - case 'SAVE_ROI': - eventEmitter.emit('saveRoi', event.data) - break default: throw new Error(`Unexpected event type: ${event.type}`) } diff --git a/src/createViewerMachine.js b/src/createViewerMachine.js index 2ae133e6..cfea949c 100644 --- a/src/createViewerMachine.js +++ b/src/createViewerMachine.js @@ -363,10 +363,7 @@ const createViewerMachine = (options, context, eventEmitterCallback) => { actions: [forwardTo('ui'), forwardTo('rendering')], }, DOWNLOAD_IMAGE: { - actions: ['downloadImage'], - }, - SAVE_ROI: { - actions: [forwardTo('eventEmitter')], + actions: [forwardTo('rendering')], }, }, }, diff --git a/src/viewerMachineOptions.js b/src/viewerMachineOptions.js index b48c82a1..3cd3a201 100644 --- a/src/viewerMachineOptions.js +++ b/src/viewerMachineOptions.js @@ -8,17 +8,6 @@ const ViewerMachineOptions = { actions: { createRenderingViewContainers, styleRenderingViewContainers, - downloadImage: (context, event) => { - const { croppingPlanes } = context.main - context.service.send({ - type: 'SAVE_ROI', - data: { - name: event.data.name, - layerName: event.data.layerName, - croppingPlanes, - }, - }) - }, }, ui: referenceUIMachineOptions,