diff --git a/package-lock.json b/package-lock.json index 975e9dc69..5dfeff376 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "itk-image-io": "^1.0.0-b.15", "itk-mesh-io": "^1.0.0-b.15", "itk-viewer-color-maps": "^1.0.3", - "itk-viewer-icons": "^11.12.0", + "itk-viewer-icons": "^11.13.0", "itk-viewer-transfer-function-editor": "^1.1.4", "itk-wasm": "^1.0.0-b.17", "mobx": "^5.15.7", @@ -12394,9 +12394,9 @@ "integrity": "sha512-pEFkHHSXP2kqbeF9kDc3dKMJgDZGwjFBVElhpFeA64BLE5V1gfc9vPxCy1JvmVurIju995rrgCGtSlhJLW5I/A==" }, "node_modules/itk-viewer-icons": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/itk-viewer-icons/-/itk-viewer-icons-11.12.0.tgz", - "integrity": "sha512-Utd13l7zQbgPmc7OPFdNLAblv/PHx0kN1mSt0bvPnWYWqDFs6BQF2uY5JxtGrJk6tfjgNYb2NT1uHCwYY2YQRQ==" + "version": "11.13.0", + "resolved": "https://registry.npmjs.org/itk-viewer-icons/-/itk-viewer-icons-11.13.0.tgz", + "integrity": "sha512-NsSz2oq5pdSXXZ3hw7hrw1stg+WOOpYuYFecXeCZZ8nr3vcxHk/N0aBWfTsB9Rcd85MxO2wJI6pmXRABMmgqfA==" }, "node_modules/itk-viewer-transfer-function-editor": { "version": "1.1.4", @@ -34022,9 +34022,9 @@ "integrity": "sha512-pEFkHHSXP2kqbeF9kDc3dKMJgDZGwjFBVElhpFeA64BLE5V1gfc9vPxCy1JvmVurIju995rrgCGtSlhJLW5I/A==" }, "itk-viewer-icons": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/itk-viewer-icons/-/itk-viewer-icons-11.12.0.tgz", - "integrity": "sha512-Utd13l7zQbgPmc7OPFdNLAblv/PHx0kN1mSt0bvPnWYWqDFs6BQF2uY5JxtGrJk6tfjgNYb2NT1uHCwYY2YQRQ==" + "version": "11.13.0", + "resolved": "https://registry.npmjs.org/itk-viewer-icons/-/itk-viewer-icons-11.13.0.tgz", + "integrity": "sha512-NsSz2oq5pdSXXZ3hw7hrw1stg+WOOpYuYFecXeCZZ8nr3vcxHk/N0aBWfTsB9Rcd85MxO2wJI6pmXRABMmgqfA==" }, "itk-viewer-transfer-function-editor": { "version": "1.1.4", diff --git a/package.json b/package.json index 327c758f7..9bca6db01 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "itk-image-io": "^1.0.0-b.15", "itk-mesh-io": "^1.0.0-b.15", "itk-viewer-color-maps": "^1.0.3", - "itk-viewer-icons": "^11.12.0", + "itk-viewer-icons": "^11.13.0", "itk-viewer-transfer-function-editor": "^1.1.4", "itk-wasm": "^1.0.0-b.17", "mobx": "^5.15.7", diff --git a/src/Rendering/Images/createImageRenderingActor.js b/src/Rendering/Images/createImageRenderingActor.js index 3fcac5970..bba79237f 100644 --- a/src/Rendering/Images/createImageRenderingActor.js +++ b/src/Rendering/Images/createImageRenderingActor.js @@ -168,6 +168,20 @@ const sendRenderedImageAssigned = ( }) } +const sendStartDataUpdate = context => { + context.service.send({ + type: 'START_DATA_UPDATE', + name: context.actorName, + }) +} + +const sendFinishDataUpdate = context => { + context.service.send({ + type: 'FINISH_DATA_UPDATE', + name: context.actorName, + }) +} + const eventResponses = { IMAGE_ASSIGNED: { target: 'updatingImage', @@ -273,12 +287,27 @@ const createUpdatingImageMachine = options => { states: { checkingUpdateNeeded: { always: [ - { cond: 'isImageUpdateNeeded', target: 'loadingImage' }, + { cond: 'isImageUpdateNeeded', target: 'preLoadingImage' }, { target: '#updatingImageMachine.loadedImage' }, ], exit: assign({ isUpdateForced: false }), }, + preLoadingImage: { + entry: sendStartDataUpdate, + invoke: { + id: 'preLoadingImage', + src: async () => { + // Give spinner chance to start. Waiting 2 frames works better in cached image case =| + await new Promise(requestAnimationFrame) + await new Promise(requestAnimationFrame) + }, + onDone: { + target: 'loadingImage', + }, + }, + }, + loadingImage: { invoke: { id: 'updateRenderedImage', @@ -302,6 +331,7 @@ const createUpdatingImageMachine = options => { }, loadedImage: { + entry: sendFinishDataUpdate, always: [ { cond: 'isFramerateScalePickingOn', @@ -349,10 +379,11 @@ const createUpdatingImageMachine = options => { } const createImageRenderingActor = (options, context, name) => { + const machineContext = { ...context, actorName: name } return createMachine( { id: 'imageRendering', - context: { ...context, actorName: name }, + context: machineContext, type: 'parallel', states: { imageLoader: { @@ -378,11 +409,12 @@ const createImageRenderingActor = (options, context, name) => { UPDATE_IMAGE_HISTOGRAM: {}, RENDERED_BOUNDS_CHANGED: {}, }, + entry: 'assignVisualizedComponents', invoke: { id: 'updatingImageMachine', src: createUpdatingImageMachine(options), data: { - ...context, + ...machineContext, hasScaledCoarser: false, targetScale: ({ images }, event) => { if (event.type === 'SET_IMAGE_SCALE') diff --git a/src/Rendering/Images/createImagesRenderingMachine.js b/src/Rendering/Images/createImagesRenderingMachine.js index a1e801f58..c0f34c75e 100644 --- a/src/Rendering/Images/createImagesRenderingMachine.js +++ b/src/Rendering/Images/createImagesRenderingMachine.js @@ -25,14 +25,14 @@ function spawnImageRenderingActor(options) { }) } -const sendEventToAllActors = () => - actions.pure(({ images: { imageRenderingActors } }, event) => +const sendEventToAllActors = actions.pure( + ({ images: { imageRenderingActors } }, event) => Array.from(imageRenderingActors.values()).map(actor => send(event, { to: actor, }) ) - ) +) function createImagesRenderingMachine(options, context) { const { imageRenderingActor } = options @@ -196,7 +196,7 @@ function createImagesRenderingMachine(options, context) { }, ...makeTransitions( ['CROPPING_PLANES_CHANGED_BY_USER', 'CAMERA_MODIFIED'], - { actions: sendEventToAllActors() } + { actions: sendEventToAllActors } ), }, }, diff --git a/src/Rendering/VTKJS/Images/applyComponentVisibility.js b/src/Rendering/VTKJS/Images/applyComponentVisibility.js index d276e6a5e..0315134dd 100644 --- a/src/Rendering/VTKJS/Images/applyComponentVisibility.js +++ b/src/Rendering/VTKJS/Images/applyComponentVisibility.js @@ -14,6 +14,7 @@ function applyComponentVisibility(context, event) { const weight = visibility ? 1.0 : 0.0 if (visibility && visualizedComponents.indexOf(index) < 0) { + // add component to visualizedComponents visualizedComponents.push(index) for (let i = 0; i < visualizedComponents.length; i++) { if (!componentVisibilities[visualizedComponents[i]]) { @@ -40,7 +41,7 @@ function applyComponentVisibility(context, event) { volumeActors.forEach(volume => { const volumeProperty = volume.getProperty() - if (!!context.images.labelImage || !!context.images.editorLabelImage) { + if (context.images.labelImage || context.images.editorLabelImage) { let componentsVisible = false for (let i = 0; i < visualizedComponents.length - 1; i++) { componentsVisible = componentsVisible[i] ? true : componentsVisible diff --git a/src/Rendering/VTKJS/Images/assignVisualizedComponents.js b/src/Rendering/VTKJS/Images/assignVisualizedComponents.js new file mode 100644 index 000000000..7d0ee29c3 --- /dev/null +++ b/src/Rendering/VTKJS/Images/assignVisualizedComponents.js @@ -0,0 +1,56 @@ +import { assign } from 'xstate' + +const assignVisualizedComponents = assign({ + images: context => { + const name = context.actorName + const actorContext = context.images.actorContext.get(name) + const image = actorContext.image + const labelImage = actorContext.labelImage + const editorLabelImage = actorContext.editorLabelImage + if (image) { + const imageComponents = image.imageType.components + actorContext.visualizedComponents = Array(image.imageType.components) + .fill(0) + .map((_, idx) => idx) + .filter(i => actorContext.componentVisibilities[i]) + + actorContext.maxIntensityComponents = 4 + if (labelImage) { + actorContext.maxIntensityComponents -= 1 + } + if (editorLabelImage) { + actorContext.maxIntensityComponents -= 1 + } + + const numVizComps = Math.min( + imageComponents, + actorContext.maxIntensityComponents + ) + if (actorContext.visualizedComponents.length > numVizComps) { + // turn off unrenderable components + actorContext.visualizedComponents = actorContext.visualizedComponents.slice( + 0, + numVizComps + ) + const offComps = [...Array(imageComponents).keys()].filter( + comp => !actorContext.visualizedComponents.includes(comp) + ) + offComps.forEach(comp => + context.service.send({ + type: 'IMAGE_COMPONENT_VISIBILITY_CHANGED', + data: { name, component: comp, visibility: false }, + }) + ) + } + } + if (labelImage) { + actorContext.visualizedComponents = + actorContext.visualizedComponents ?? [] + actorContext.visualizedComponents.push(-1) + } + + return context.images + }, +}) + +export default assignVisualizedComponents diff --git a/src/Rendering/VTKJS/Images/imagesRenderingMachineOptions.js b/src/Rendering/VTKJS/Images/imagesRenderingMachineOptions.js index 07c3dde29..9d4a54bd9 100644 --- a/src/Rendering/VTKJS/Images/imagesRenderingMachineOptions.js +++ b/src/Rendering/VTKJS/Images/imagesRenderingMachineOptions.js @@ -24,6 +24,7 @@ import mapToPiecewiseFunctionNodes from './mapToPiecewiseFunctionNodes' import { getBoundsOfFullImage } from '../Main/croppingPlanes' import { computeRenderedBounds } from '../Main/computeRenderedBounds' import { applyCinematicChanged } from './applyCinematicChanged' +import assignVisualizedComponents from './assignVisualizedComponents' const EPSILON = 0.000001 @@ -69,6 +70,7 @@ const imagesRenderingMachineOptions = { actions: { applyRenderedImage, assignRenderedImage, + assignVisualizedComponents, toggleLayerVisibility, diff --git a/src/Rendering/VTKJS/Images/updateRenderedImage.js b/src/Rendering/VTKJS/Images/updateRenderedImage.js index 050d0aaa0..f9fbb7de5 100644 --- a/src/Rendering/VTKJS/Images/updateRenderedImage.js +++ b/src/Rendering/VTKJS/Images/updateRenderedImage.js @@ -1,7 +1,6 @@ import vtkITKHelper from 'vtk.js/Sources/Common/DataModel/ITKHelper' import { mat4 } from 'gl-matrix' -import updateVisualizedComponents from './updateVisualizedComponents' import { fuseImages } from './fuseImages' import { computeRenderedBounds } from '../Main/computeRenderedBounds' import { worldBoundsToIndexBounds } from '../../../IO/MultiscaleSpatialImage' @@ -47,8 +46,6 @@ async function updateRenderedImage(context) { const name = context.images.updateRenderedName const actorContext = context.images.actorContext.get(name) - updateVisualizedComponents(context, name) - const { image, labelImage, diff --git a/src/Rendering/VTKJS/Images/updateVisualizedComponents.js b/src/Rendering/VTKJS/Images/updateVisualizedComponents.js deleted file mode 100644 index b5711e20a..000000000 --- a/src/Rendering/VTKJS/Images/updateVisualizedComponents.js +++ /dev/null @@ -1,48 +0,0 @@ -function updateVisualizedComponents(context, name) { - const actorContext = context.images.actorContext.get(name) - const image = actorContext.image - const labelImage = actorContext.labelImage - const editorLabelImage = actorContext.editorLabelImage - if (image) { - const imageComponents = image.imageType.components - actorContext.visualizedComponents = Array(image.imageType.components) - .fill(0) - .map((_, idx) => idx) - .filter(i => actorContext.componentVisibilities[i]) - - actorContext.maxIntensityComponents = 4 - if (labelImage) { - actorContext.maxIntensityComponents -= 1 - } - if (editorLabelImage) { - actorContext.maxIntensityComponents -= 1 - } - - const numVizComps = Math.min( - imageComponents, - actorContext.maxIntensityComponents - ) - if (actorContext.visualizedComponents.length > numVizComps) { - // turn off unrenderable components - actorContext.visualizedComponents = actorContext.visualizedComponents.slice( - 0, - numVizComps - ) - const offComps = [...Array(imageComponents).keys()].filter( - comp => !actorContext.visualizedComponents.includes(comp) - ) - offComps.forEach(comp => - context.service.send({ - type: 'IMAGE_COMPONENT_VISIBILITY_CHANGED', - data: { name, component: comp, visibility: false }, - }) - ) - } - } - if (labelImage) { - actorContext.visualizedComponents = actorContext.visualizedComponents ?? [] - actorContext.visualizedComponents.push(-1) - } -} - -export default updateVisualizedComponents diff --git a/src/Rendering/VTKJS/createRenderer.js b/src/Rendering/VTKJS/createRenderer.js index bbac9cd33..e3e3e617e 100644 --- a/src/Rendering/VTKJS/createRenderer.js +++ b/src/Rendering/VTKJS/createRenderer.js @@ -1,5 +1,5 @@ -import vtkProxyManager from 'vtk.js/Sources/Proxy/Core/ProxyManager' -import proxyConfiguration from './proxyManagerConfiguration' +// import vtkProxyManager from 'vtk.js/Sources/Proxy/Core/ProxyManager' +// import proxyConfiguration from './proxyManagerConfiguration' import createMainRenderer from './Main/createMainRenderer' // Load the rendering pieces we want to use (for both WebGL and WebGPU) @@ -19,6 +19,9 @@ function createRenderer(context) { context.itkVtkView.setXyLowerLeft(context.xyLowerLeft) createMainRenderer(context) + + const interactor = context.itkVtkView.getInteractor() + interactor.onRenderEvent(() => context.service.send('POST_RENDER')) } export default createRenderer diff --git a/src/UI/Images/createImagesUIMachine.js b/src/UI/Images/createImagesUIMachine.js index 84a3fb1b5..bd1dbfc11 100644 --- a/src/UI/Images/createImagesUIMachine.js +++ b/src/UI/Images/createImagesUIMachine.js @@ -24,13 +24,11 @@ const assignComponentVisibility = assign({ // A component was made visible, and it was not already in the list // of visualized components const currentNumVisualized = componentVisibilities.reduce( - (a, c) => c + a, + (count, isVisible) => count + isVisible, 0 ) if (currentNumVisualized + 1 > actorContext.maxIntensityComponents) { - // Find the index in the visualized components list of the last touched - // component. We need to replace it with this component the user just - // turned on. + // Replace last touched component with turned on component componentVisibilities[ actorContext.lastComponentVisibilityChanged ] = false diff --git a/src/UI/Layers/createLayerUIActor.js b/src/UI/Layers/createLayerUIActor.js index 741ffef54..b3e677052 100644 --- a/src/UI/Layers/createLayerUIActor.js +++ b/src/UI/Layers/createLayerUIActor.js @@ -11,20 +11,15 @@ const assignLayerVisibility = assign({ }, }) -const createLayerUIActor = (options, context) => { +const createLayerUIActor = (options, context, actorContext) => { return Machine( { id: 'layerUI', - initial: 'idle', - context, + initial: 'active', + context: { actorContext, ...context }, states: { - idle: { - always: { - target: 'active', - actions: 'createLayerInterface', - }, - }, active: { + entry: 'createLayerInterface', on: { SELECT_LAYER: { actions: 'selectLayer', @@ -33,12 +28,21 @@ const createLayerUIActor = (options, context) => { actions: [assignLayerVisibility, 'toggleLayerVisibility'], }, }, - }, - finished: { - type: 'final', - }, - onDone: { - //actions: 'cleanup' + initial: 'idle', + states: { + idle: { + entry: 'finishDataUpdate', + on: { START_DATA_UPDATE: 'dataUpdating' }, + }, + dataUpdating: { + entry: 'startDataUpdate', + on: { FINISH_DATA_UPDATE: 'dataLoading' }, + }, + dataLoading: { + // wait until data is loaded on GPU + on: { POST_RENDER: 'idle' }, + }, + }, }, }, }, diff --git a/src/UI/Layers/createLayersUIMachine.js b/src/UI/Layers/createLayersUIMachine.js index ce1e6451c..3e0831708 100644 --- a/src/UI/Layers/createLayersUIMachine.js +++ b/src/UI/Layers/createLayersUIMachine.js @@ -1,4 +1,4 @@ -import { Machine, assign, spawn, send } from 'xstate' +import { Machine, assign, spawn, send, actions } from 'xstate' import { PixelTypes } from 'itk-wasm' @@ -33,7 +33,11 @@ function spawnLayerRenderingActor(options) { layers.lastAddedData = { name, data: event.data } layers.layerUIActors.set( name, - spawn(createLayerUIActor(options, context), `layerUIActor-${name}`) + spawn( + createLayerUIActor(options, context, actorContext), + `layerUIActor-${name}`, + actorContext + ) ) break } @@ -52,7 +56,10 @@ function spawnLayerRenderingActor(options) { layers.lastAddedData = { name, data: event.data } layers.layerUIActors.set( name, - spawn(createLayerUIActor(options, context), `layerUIActor-${name}`) + spawn( + createLayerUIActor(options, context, actorContext), + `layerUIActor-${name}` + ) ) break } @@ -222,6 +229,19 @@ const assignSelectedName = assign({ }, }) +const forwardToNamedActor = send((_, e) => e, { + to: (c, e) => `layerUIActor-${e.name}`, +}) + +const sendEventToAllActors = actions.pure( + ({ layers: { layerUIActors } }, event) => + Array.from(layerUIActors?.values() ?? []).map(actor => + send(event, { + to: actor, + }) + ) +) + function createLayersUIMachine(options, context) { const { layerUIActor } = options @@ -282,6 +302,9 @@ function createLayersUIMachine(options, context) { }), ], }, + START_DATA_UPDATE: { actions: forwardToNamedActor }, + FINISH_DATA_UPDATE: { actions: forwardToNamedActor }, + POST_RENDER: { actions: sendEventToAllActors }, }, }, }, diff --git a/src/UI/createUIMachine.js b/src/UI/createUIMachine.js index 50f86e30b..980645c63 100644 --- a/src/UI/createUIMachine.js +++ b/src/UI/createUIMachine.js @@ -179,6 +179,9 @@ function createUIMachine(options, context) { CINEMATIC_CHANGED: { actions: forwardTo('images'), }, + START_DATA_UPDATE: { actions: forwardTo('layers') }, + FINISH_DATA_UPDATE: { actions: forwardTo('layers') }, + POST_RENDER: { actions: forwardTo('layers') }, }, states: { // Optional feature of the user interface diff --git a/src/UI/reference-ui/dist/referenceUIMachineOptions.js b/src/UI/reference-ui/dist/referenceUIMachineOptions.js index e230102f0..5b0675116 100644 --- a/src/UI/reference-ui/dist/referenceUIMachineOptions.js +++ b/src/UI/reference-ui/dist/referenceUIMachineOptions.js @@ -28,7 +28,7 @@ function styleInject(css, ref) { } var css_248z$1 = - ".ItkVtkViewer-module_loading__11c63 {\n border: 16px solid #f3f3f3; /* Light grey */\n border-top: 16px solid #3498db; /* Blue */\n border-radius: 50%;\n width: 120px;\n height: 120px;\n position: absolute;\n left: calc(50% - 60px);\n top: calc(50% - 60px);\n -webkit-animation: ItkVtkViewer-module_spin__mT5S6 2s linear infinite;\n animation: ItkVtkViewer-module_spin__mT5S6 2s linear infinite;\n box-sizing: border-box;\n}\n\n@-webkit-keyframes ItkVtkViewer-module_spin__mT5S6 {\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(360deg);\n }\n}\n\n@keyframes ItkVtkViewer-module_spin__mT5S6 {\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(360deg);\n }\n}\n\n.ItkVtkViewer-module_viewContainer__-5zNz {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n right: 0;\n display: flex;\n flex-direction: column;\n background: rgba(128, 128, 128, 0.8);\n}\n\n.ItkVtkViewer-module_viewport__BMgOt {\n position: relative;\n flex: 1;\n min-height: 0;\n}\n\n.ItkVtkViewer-module_uiContainer__CiawP { \n position: absolute;\n top: 0;\n left: 0;\n z-index: 1000;\n overflow-x: hidden;\n max-height: 100%;\n\n display: flex;\n align-items: stretch;\n flex-direction: column;\n padding: 6px 0 0 6px;\n border: 0px;\n box-sizing: border-box;\n}\n\n.ItkVtkViewer-module_uiGroup__ad-WI {\n background: rgba(128, 128, 128, 0.5);\n border-radius: 4px;\n margin: 2px;\n}\n\n.ItkVtkViewer-module_uiRow__KTQa8 {\n display: flex;\n flex-direction: row;\n flex: 1;\n align-items: center;\n justify-content: space-between;\n padding: 5px;\n}\n\n.ItkVtkViewer-module_mainUIRow__vTXih {\n justify-content: space-around;\n max-width: 420px;\n}\n\n.ItkVtkViewer-module_planeUIRow__D5gCh {\n background: rgba(128, 128, 128, 0.5);\n}\n\n.ItkVtkViewer-module_layersUIRow__0LDm5 {\n justify-content: space-around;\n max-width: 420px;\n}\n\n.ItkVtkViewer-module_progress__WydXH {\n color: white;\n font-size: 200%;\n height: 100vh;\n width: 100vw;\n text-align: center;\n vertical-align: middle;\n line-height: 100vh;\n}\n\n.ItkVtkViewer-module_piecewiseWidget__5gKl5 {\n flex: 1;\n background: rgba(255, 255, 255, 0.2);\n border-radius: 3px;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_logo__9ErCF {\n position: absolute;\n top: 5px;\n right: 5px;\n height: 2em;\n width: 2em;\n cursor: pointer;\n z-index: 100;\n}\n\n.ItkVtkViewer-module_fpsMonitor__bnwqr {\n position: absolute;\n top: 5px;\n right: 5px;\n border-radius: 5px;\n background: rgba(255, 255, 255, 0.6);\n cursor: pointer;\n z-index: 101;\n}\n\n[itk-vtk-tooltip] {\n position: relative;\n}\n[itk-vtk-tooltip]::before {\n content: attr(itk-vtk-tooltip-content);\n visibility: hidden;\n position: absolute;\n top: 50%;\n right: calc(100% + 16px);\n width: 400%;\n padding: 4px 6px;\n text-align: center;\n text-transform: none;\n font-size: 0.9em;\n font-family: monospace;\n border-radius: 3px;\n background: rgba(0.9, 0.9, 0.9, 0.95);\n color: white;\n opacity: 0;\n transform: translate(15px, -50%);\n transition-property: all;\n transition-duration: 0.3s;\n transition-timing-function: ease-in-out;\n transition-delay: 0.8s;\n z-index: 1;\n}\n\n[itk-vtk-tooltip]:hover::before {\n opacity: 1;\n visibility: visible;\n transform: translate(0, -50%);\n}\n\n[itk-vtk-tooltip-bottom]::before {\n top: calc(100% + 16px);\n left: 50%;\n right: initial;\n transform: translate(-50%, -15px);\n}\n[itk-vtk-tooltip-bottom]:hover::before {\n transform: translate(-50%, 0);\n}\n[itk-vtk-tooltip-right]::before {\n top: 50%;\n left: calc(100% + 16px);\n right: initial;\n transform: translate(-15px, -50%);\n}\n[itk-vtk-tooltip-right]:hover::before {\n transform: translate(0, -50%);\n}\n\n[itk-vtk-tooltip-top-screenshot]::before {\n top: initial;\n left: 260%;\n right: initial;\n bottom: calc(100% + 8px);\n transform: translate(-50%, 15px);\n}\n[itk-vtk-tooltip-top-screenshot]:hover::before {\n transform: translate(-50%, 0);\n}\n[itk-vtk-tooltip-top-annotations]::before {\n top: initial;\n left: 160%;\n right: initial;\n bottom: calc(100% + 10px);\n transform: translate(-50%, 15px);\n}\n[itk-vtk-tooltip-top-annotations]:hover::before {\n transform: translate(-50%, 0);\n}\n[itk-vtk-tooltip-top-axes]::before {\n top: initial;\n left: 160%;\n right: initial;\n bottom: calc(100% + 10px);\n transform: translate(-50%, 15px);\n}\n[itk-vtk-tooltip-top-axes]:hover::before {\n transform: translate(-50%, 0);\n}\n[itk-vtk-tooltip-top-fullscreen]::before {\n top: initial;\n left: 120%;\n right: initial;\n bottom: calc(100% + 10px);\n transform: translate(-50%, 15px);\n width: 400%;\n}\n[itk-vtk-tooltip-top-fullscreen]:hover::before {\n transform: translate(-50%, 0);\n}\n[itk-vtk-tooltip-top]::before {\n top: initial;\n left: 60%;\n right: initial;\n bottom: calc(100% + 10px);\n transform: translate(-50%, 15px);\n}\n[itk-vtk-tooltip-top]:hover::before {\n transform: translate(-50%, 0);\n}\n[itk-vtk-tooltip-top-fullscreen]::before {\n top: initial;\n left: 120%;\n right: initial;\n bottom: calc(100% + 10px);\n transform: translate(-50%, 15px);\n width: 400%;\n}\n\n.ItkVtkViewer-module_layerEntryCommon__oIE1u {\n flex: 1;\n display: flex;\n flex-direction: row;\n align-items: stretch;\n justify-content: space-between;\n border-style: solid;\n border-width: 2px;\n border-radius: 10%;\n}\n\n.ItkVtkViewer-module_layerEntryBrightBG__qXyI2 {\n border-color: #666;\n}\n\n.ItkVtkViewer-module_layerEntryDarkBG__BmiCj {\n border-color: #aaa;\n}\n\n.ItkVtkViewer-module_layerLabelCommon__kTiO9 {\n border: none;\n background: transparent;\n font-size: 1.2em;\n margin-right: 10px;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_layerLabelBrightBG__vAfex {\n color: black;\n}\n\n.ItkVtkViewer-module_layerLabelDarkBG__sM6Bg {\n color: white;\n}\n\n.ItkVtkViewer-module_visibleButton__ezrIc {\n flex-basis: 2.5em;\n cursor: pointer;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_visibleButton__ezrIc img {\n height: 1.2em;\n width: 1.2em;\n padding-top: 2px;\n padding-bottom: 2px;\n padding-left: 6px;\n padding-right: 6px;\n}\n\n.ItkVtkViewer-module_layerIcon__v-rxO img {\n height: 1.2em;\n width: 1.2em;\n padding-top: 2px;\n padding-bottom: 2px;\n padding-left: 8px;\n padding-right: 6px;\n}\n\n.ItkVtkViewer-module_tooltipButtonBrightBG__yffVf::before {\n}\n\n.ItkVtkViewer-module_tooltipButtonDarkBG__gEu0i::before {\n filter: invert(100%);\n -webkit-filter: invert(100%);\n}\n\n.ItkVtkViewer-module_invertibleButtonBrightBG__VmIfT {\n}\n\n.ItkVtkViewer-module_invertibleButtonDarkBG__GoKgD {\n filter: invert(100%);\n -webkit-filter: invert(100%);\n}\n\n.ItkVtkViewer-module_collapseUIButton__Ac6-L {\n width: 1.5em;\n cursor: pointer;\n}\n\n.ItkVtkViewer-module_screenshotButton__OL4Na {\n flex: 1;\n width: 8mm;\n padding-top: 2px;\n padding-bottom: 2px;\n padding-left: 6px;\n padding-right: 6px;\n cursor: pointer;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_screenshotButton__OL4Na img {\n height: 1.2em;\n width: 1.2em;\n}\n\n.ItkVtkViewer-module_annotationsButton__Msb-p {\n flex: 1;\n width: 8mm;\n padding-top: 2px;\n padding-bottom: 2px;\n padding-left: 6px;\n padding-right: 6px;\n cursor: pointer;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_annotationsButton__Msb-p img {\n height: 1.2em;\n width: 1.2em;\n}\n\n.ItkVtkViewer-module_axesButton__k2H6p {\n flex: 1;\n width: 8mm;\n padding-top: 2px;\n padding-bottom: 2px;\n padding-left: 6px;\n padding-right: 6px;\n cursor: pointer;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_axesButton__k2H6p img {\n height: 1.2em;\n width: 1.2em;\n}\n\n.ItkVtkViewer-module_fullscreenButton__en3Z5 {\n flex: 1;\n width: 8m;\n padding-top: 2px;\n padding-bottom: 2px;\n padding-left: 6px;\n padding-right: 6px;\n cursor: pointer;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_fullscreenButton__en3Z5 img {\n height: 1.2em;\n width: 1.2em;\n}\n\n.ItkVtkViewer-module_interpolationButton__2P0HJ {\n flex: 1;\n width: 8mm;\n padding-top: 2px;\n padding-bottom: 2px;\n padding-left: 2px;\n padding-right: 4px;\n cursor: pointer;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_interpolationButton__2P0HJ img {\n width: 1.2em;\n}\n\n.ItkVtkViewer-module_cropButton__ljwuU {\n flex: 1;\n height: 8mm;\n padding-top: 2px;\n padding-bottom: 2px;\n padding-left: 6px;\n padding-right: 6px;\n cursor: pointer;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_cropButton__ljwuU img {\n height: 1.2em;\n width: 1.2em;\n}\n\n.ItkVtkViewer-module_resetCropButton__SCGTH {\n flex: 1;\n width: 8mm;\n padding-top: 2px;\n padding-bottom: 2px;\n padding-left: 6px;\n padding-right: 6px;\n cursor: pointer;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_resetCropButton__SCGTH img {\n height: 1.2em;\n width: 1.2em;\n}\n\n.ItkVtkViewer-module_distanceEntry__zXMUS {\n flex: 1;\n display: flex;\n flex-direction: row;\n align-items: self-start;\n}\n\n.ItkVtkViewer-module_distanceButton__NhxBT {\n flex: 1;\n height: 8mm;\n padding-top: 2px;\n padding-bottom: 2px;\n padding-left: 6px;\n padding-right: 6px;\n cursor: pointer;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_distanceButton__NhxBT img {\n height: 1.2em;\n width: 1.2em;\n}\n\n.ItkVtkViewer-module_distanceLabelCommon__Ec-uc {\n border: none;\n background: transparent;\n font-size: 1.2em;\n margin-right: 10px;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_distanceLabelBrightBG__aYmfG {\n color: black;\n}\n\n.ItkVtkViewer-module_distanceLabelDarkBG__kYXvI {\n color: white;\n}\n\n.ItkVtkViewer-module_distanceInput__gyNaU {\n background: transparent;\n color: white;\n font-size: 1em;\n width: 80px;\n}\n\n.ItkVtkViewer-module_resetCameraButton__l9FGp {\n flex: 1;\n width: 8mm;\n padding-top: 2px;\n padding-bottom: 2px;\n padding-left: 6px;\n padding-right: 6px;\n cursor: pointer;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_resetCameraButton__l9FGp img {\n height: 1.2em;\n width: 1.2em;\n}\n\n.ItkVtkViewer-module_bgColorButton__yrjOX {\n flex: 1;\n width: 8mm;\n padding-top: 2px;\n padding-bottom: 2px;\n padding-left: 6px;\n padding-right: 6px;\n cursor: pointer;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_bgColorButton__yrjOX img {\n height: 1.2em;\n width: 1.2em;\n}\n\n.ItkVtkViewer-module_viewModeButton__OtTng {\n flex: 1;\n width: 8mm;\n padding-top: 2px;\n padding-bottom: 2px;\n padding-left: 6px;\n padding-right: 6px;\n cursor: pointer;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_viewModeButton__OtTng img {\n width: 1.3em;\n height: 1.3em;\n}\n\n.ItkVtkViewer-module_shadowButton__09fEk {\n width: 8mm;\n padding: 4px;\n padding-left: 0px;\n cursor: pointer;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_shadowButton__09fEk img {\n width: 1.3em;\n height: 1.3em;\n}\n\n.ItkVtkViewer-module_viewPlanesButton__rSnuZ {\n flex: 1;\n width: 8mm;\n padding-top: 2px;\n padding-bottom: 2px;\n padding-left: 0px;\n padding-right: 6px;\n cursor: pointer;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_viewPlanesButton__rSnuZ img {\n width: 1.3em;\n height: 1.3em;\n}\n\n.ItkVtkViewer-module_toggleInput__jHLTo {\n margin: 0px;\n width: 0;\n opacity: 0;\n box-sizing: content-box;\n}\n\n.ItkVtkViewer-module_toggleButton__qHhHZ {\n cursor: pointer;\n border-radius: 0.2em;\n opacity: 0.45;\n}\n\ninput:checked.ItkVtkViewer-module_toggleInput__jHLTo + label {\n opacity: 1;\n}\n\n.ItkVtkViewer-module_numberInput__pDxYH {\n color: white;\n background: transparent;\n font-size: 1em;\n padding-left: 2px;\n width: 70px;\n}\n\n.ItkVtkViewer-module_selector__yw8l- {\n display: flex;\n direction: row;\n font-size: 1.2em;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_componentTab__6KSJF {\n position: absolute;\n opacity: 0;\n pointer-events: none;\n}\n\n.ItkVtkViewer-module_disableInterface__CGB4S {\n pointer-events: none;\n opacity: 0.5;\n}\n\n.ItkVtkViewer-module_componentTab__6KSJF + .ItkVtkViewer-module_compTabLabel__8u4iU {\n background: rgba(40, 40, 40, 0.5);\n padding: 5px;\n margin-right: 2px;\n border-radius: 5px 5px 0px 0px;\n color: #777;\n}\n\n.ItkVtkViewer-module_componentTab__6KSJF:hover + .ItkVtkViewer-module_compTabLabel__8u4iU {\n background: rgba(90, 90, 90, 0.5);\n}\n\n.ItkVtkViewer-module_componentTab__6KSJF:checked + .ItkVtkViewer-module_compTabLabel__8u4iU {\n background: rgba(127, 127, 127, 0.5);\n color: #fff;\n}\n\n.ItkVtkViewer-module_componentVisibility__y1rRS {\n position: relative;\n top: -2px;\n margin-left: 10px;\n}\n\nselect {\n -moz-appearance: none;\n}\n\nselect option {\n color: black;\n}\n\nselect:focus {\n outline: none;\n border: none;\n}\n\n.ItkVtkViewer-module_sampleDistanceButton__NjT0o {\n width: 8mm;\n padding: 4px;\n padding-left: 6px;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_sampleDistanceButton__NjT0o img {\n width: 1.2em;\n height: 1.2em;\n}\n\n.ItkVtkViewer-module_sliderColumn__ZwISb {\n display: flex;\n flex-direction: column;\n flex: 1;\n padding: 0 5px;\n}\n\n.ItkVtkViewer-module_sliderIcon__jfoL- {\n width: 1.8em;\n margin-right: 10px;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_blendModeButton__cit1w {\n width: 8mm;\n padding: 4px;\n padding-left: 8px;\n padding-right: 0px;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_blendModeButton__cit1w img {\n width: 1.2em;\n height: 1.2em;\n}\n\n.ItkVtkViewer-module_gradientOpacitySlider__wkEqP {\n width: 8mm;\n padding: 4px;\n padding-left: 6px;\n padding-right: 0px;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_gradientOpacitySlider__wkEqP img {\n width: 1.2em;\n height: 1.2em;\n}\n\n.ItkVtkViewer-module_sliderEntry__3r3gO {\n flex: 1;\n display: flex;\n flex-direction: row;\n align-items: center;\n}\n\n.ItkVtkViewer-module_slider__eT9qm {\n flex: 1;\n min-height: 1rem;\n}\n\n.ItkVtkViewer-module_planeLabel__E1zOk {\n padding-left: 6px;\n padding: 2px;\n display: block;\n font-size: 1.1em;\n font-family: monospace;\n color: black;\n border-width: 2px;\n border-radius: 10%;\n}\n\n.ItkVtkViewer-module_xPlaneLabel__wK4Cb {\n background-color: #ef5350;\n}\n\n.ItkVtkViewer-module_yPlaneLabel__rIm0j {\n background-color: #fdd835;\n}\n\n.ItkVtkViewer-module_zPlaneLabel__94NL7 {\n background-color: #4caf50;\n}\n\n.ItkVtkViewer-module_gradientOpacityScale__NrqOZ {\n z-index: 1100;\n position: relative;\n}\n\n.ItkVtkViewer-module_gradientOpacityScale__NrqOZ input {\n position: absolute;\n bottom: 20px;\n left: -24px;\n width: 12px;\n -ms-writing-mode: bt-lr;\n writing-mode: bt-lr;\n -webkit-appearance: slider-vertical;\n}\n\n.ItkVtkViewer-module_bigFileDrop__cZdkP {\n position: absolute;\n left: 50%;\n top: 50%;\n transform: translate(-50%, -50%);\n background-color: white;\n background-image: url('./dropBG.jpg');\n background-repeat: no-repeat;\n background-position: center;\n background-size: contain;\n border-radius: 10px;\n width: 50px;\n padding: calc(50vh - 2em) calc(50vw - 25px - 2em);\n}\n\n.ItkVtkViewer-module_fullscreenContainer__-H3c8 {\n position: absolute;\n width: 100vw;\n height: 100vh;\n top: 0;\n left: 0;\n overflow: hidden;\n background: black;\n margin: 0;\n padding: 0;\n}\n" + ".ItkVtkViewer-module_loading__11c63 {\n border: 16px solid #f3f3f3; /* Light grey */\n border-top: 16px solid #3498db; /* Blue */\n border-radius: 50%;\n width: 120px;\n height: 120px;\n position: absolute;\n left: calc(50% - 60px);\n top: calc(50% - 60px);\n -webkit-animation: ItkVtkViewer-module_spin__mT5S6 2s linear infinite;\n animation: ItkVtkViewer-module_spin__mT5S6 2s linear infinite;\n box-sizing: border-box;\n}\n\n@-webkit-keyframes ItkVtkViewer-module_spin__mT5S6 {\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(360deg);\n }\n}\n\n@keyframes ItkVtkViewer-module_spin__mT5S6 {\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(360deg);\n }\n}\n\n.ItkVtkViewer-module_viewContainer__-5zNz {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n right: 0;\n display: flex;\n flex-direction: column;\n background: rgba(128, 128, 128, 0.8);\n}\n\n.ItkVtkViewer-module_viewport__BMgOt {\n position: relative;\n flex: 1;\n min-height: 0;\n}\n\n.ItkVtkViewer-module_uiContainer__CiawP {\n position: absolute;\n top: 0;\n left: 0;\n z-index: 1000;\n overflow-x: hidden;\n max-height: 100%;\n\n display: flex;\n align-items: stretch;\n flex-direction: column;\n padding: 6px 0 0 6px;\n border: 0px;\n box-sizing: border-box;\n}\n\n.ItkVtkViewer-module_uiGroup__ad-WI {\n background: rgba(128, 128, 128, 0.5);\n border-radius: 4px;\n margin: 2px;\n}\n\n.ItkVtkViewer-module_uiRow__KTQa8 {\n display: flex;\n flex-direction: row;\n flex: 1;\n align-items: center;\n justify-content: space-between;\n padding: 5px;\n}\n\n.ItkVtkViewer-module_mainUIRow__vTXih {\n justify-content: space-around;\n max-width: 420px;\n}\n\n.ItkVtkViewer-module_planeUIRow__D5gCh {\n background: rgba(128, 128, 128, 0.5);\n}\n\n.ItkVtkViewer-module_layersUIRow__0LDm5 {\n justify-content: space-around;\n max-width: 420px;\n}\n\n.ItkVtkViewer-module_progress__WydXH {\n color: white;\n font-size: 200%;\n height: 100vh;\n width: 100vw;\n text-align: center;\n vertical-align: middle;\n line-height: 100vh;\n}\n\n.ItkVtkViewer-module_piecewiseWidget__5gKl5 {\n flex: 1;\n background: rgba(255, 255, 255, 0.2);\n border-radius: 3px;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_logo__9ErCF {\n position: absolute;\n top: 5px;\n right: 5px;\n height: 2em;\n width: 2em;\n cursor: pointer;\n z-index: 100;\n}\n\n.ItkVtkViewer-module_fpsMonitor__bnwqr {\n position: absolute;\n top: 5px;\n right: 5px;\n border-radius: 5px;\n background: rgba(255, 255, 255, 0.6);\n cursor: pointer;\n z-index: 101;\n}\n\n[itk-vtk-tooltip] {\n position: relative;\n}\n[itk-vtk-tooltip]::before {\n content: attr(itk-vtk-tooltip-content);\n visibility: hidden;\n position: absolute;\n top: 50%;\n right: calc(100% + 16px);\n width: 400%;\n padding: 4px 6px;\n text-align: center;\n text-transform: none;\n font-size: 0.9em;\n font-family: monospace;\n border-radius: 3px;\n background: rgba(0.9, 0.9, 0.9, 0.95);\n color: white;\n opacity: 0;\n transform: translate(15px, -50%);\n transition-property: all;\n transition-duration: 0.3s;\n transition-timing-function: ease-in-out;\n transition-delay: 0.8s;\n z-index: 1;\n}\n\n[itk-vtk-tooltip]:hover::before {\n opacity: 1;\n visibility: visible;\n transform: translate(0, -50%);\n}\n\n[itk-vtk-tooltip-bottom]::before {\n top: calc(100% + 16px);\n left: 50%;\n right: initial;\n transform: translate(-50%, -15px);\n}\n[itk-vtk-tooltip-bottom]:hover::before {\n transform: translate(-50%, 0);\n}\n[itk-vtk-tooltip-right]::before {\n top: 50%;\n left: calc(100% + 16px);\n right: initial;\n transform: translate(-15px, -50%);\n}\n[itk-vtk-tooltip-right]:hover::before {\n transform: translate(0, -50%);\n}\n\n[itk-vtk-tooltip-top-screenshot]::before {\n top: initial;\n left: 260%;\n right: initial;\n bottom: calc(100% + 8px);\n transform: translate(-50%, 15px);\n}\n[itk-vtk-tooltip-top-screenshot]:hover::before {\n transform: translate(-50%, 0);\n}\n[itk-vtk-tooltip-top-annotations]::before {\n top: initial;\n left: 160%;\n right: initial;\n bottom: calc(100% + 10px);\n transform: translate(-50%, 15px);\n}\n[itk-vtk-tooltip-top-annotations]:hover::before {\n transform: translate(-50%, 0);\n}\n[itk-vtk-tooltip-top-axes]::before {\n top: initial;\n left: 160%;\n right: initial;\n bottom: calc(100% + 10px);\n transform: translate(-50%, 15px);\n}\n[itk-vtk-tooltip-top-axes]:hover::before {\n transform: translate(-50%, 0);\n}\n[itk-vtk-tooltip-top-fullscreen]::before {\n top: initial;\n left: 120%;\n right: initial;\n bottom: calc(100% + 10px);\n transform: translate(-50%, 15px);\n width: 400%;\n}\n[itk-vtk-tooltip-top-fullscreen]:hover::before {\n transform: translate(-50%, 0);\n}\n[itk-vtk-tooltip-top]::before {\n top: initial;\n left: 60%;\n right: initial;\n bottom: calc(100% + 10px);\n transform: translate(-50%, 15px);\n}\n[itk-vtk-tooltip-top]:hover::before {\n transform: translate(-50%, 0);\n}\n[itk-vtk-tooltip-top-fullscreen]::before {\n top: initial;\n left: 120%;\n right: initial;\n bottom: calc(100% + 10px);\n transform: translate(-50%, 15px);\n width: 400%;\n}\n\n.ItkVtkViewer-module_layerEntryCommon__oIE1u {\n flex: 1;\n display: flex;\n flex-direction: row;\n align-items: stretch;\n justify-content: space-between;\n border-style: solid;\n border-width: 2px;\n}\n\n.ItkVtkViewer-module_layerEntryBrightBG__qXyI2 {\n border-color: #666;\n}\n\n.ItkVtkViewer-module_layerEntryDarkBG__BmiCj {\n border-color: #aaa;\n}\n\n.ItkVtkViewer-module_layerLabelCommon__kTiO9 {\n border: none;\n background: transparent;\n font-size: 1.2em;\n margin-right: 10px;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_layerLabelBrightBG__vAfex {\n color: black;\n}\n\n.ItkVtkViewer-module_layerLabelDarkBG__sM6Bg {\n color: white;\n}\n\n.ItkVtkViewer-module_visibleButton__ezrIc {\n flex-basis: 2.5em;\n cursor: pointer;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_visibleButton__ezrIc img {\n height: 1.2em;\n width: 1.2em;\n padding-top: 2px;\n padding-bottom: 2px;\n padding-left: 6px;\n padding-right: 6px;\n}\n\n.ItkVtkViewer-module_layerIcon__v-rxO {\n display: inline-block;\n}\n\n.ItkVtkViewer-module_layerIcon__v-rxO img {\n height: 1.2em;\n width: 1.2em;\n padding-top: 2px;\n padding-bottom: 2px;\n padding-left: 8px;\n padding-right: 6px;\n}\n\n.ItkVtkViewer-module_iconGroup__qqZrW {\n display: inline-block;\n}\n\n.ItkVtkViewer-module_ldsRing__QT1wT {\n display: inline-block;\n position: relative;\n width: 20px;\n height: 20px;\n}\n.ItkVtkViewer-module_ldsRing__QT1wT div {\n box-sizing: border-box;\n display: block;\n position: absolute;\n width: 1em;\n height: 1em;\n margin: 0;\n border: 0.15em solid #000;\n border-radius: 50%;\n -webkit-animation: ItkVtkViewer-module_ldsRing__QT1wT 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;\n animation: ItkVtkViewer-module_ldsRing__QT1wT 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;\n border-color: #000 transparent transparent transparent;\n}\n.ItkVtkViewer-module_ldsRing__QT1wT div:nth-child(1) {\n -webkit-animation-delay: -0.45s;\n animation-delay: -0.45s;\n}\n.ItkVtkViewer-module_ldsRing__QT1wT div:nth-child(2) {\n -webkit-animation-delay: -0.3s;\n animation-delay: -0.3s;\n}\n.ItkVtkViewer-module_ldsRing__QT1wT div:nth-child(3) {\n -webkit-animation-delay: -0.15s;\n animation-delay: -0.15s;\n}\n@-webkit-keyframes ItkVtkViewer-module_ldsRing__QT1wT {\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(360deg);\n }\n}\n@keyframes ItkVtkViewer-module_ldsRing__QT1wT {\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(360deg);\n }\n}\n\n.ItkVtkViewer-module_tooltipButtonBrightBG__yffVf::before {\n}\n\n.ItkVtkViewer-module_tooltipButtonDarkBG__gEu0i::before {\n filter: invert(100%);\n -webkit-filter: invert(100%);\n}\n\n.ItkVtkViewer-module_invertibleButtonBrightBG__VmIfT {\n}\n\n.ItkVtkViewer-module_invertibleButtonDarkBG__GoKgD {\n filter: invert(100%);\n -webkit-filter: invert(100%);\n}\n\n.ItkVtkViewer-module_collapseUIButton__Ac6-L {\n width: 1.5em;\n cursor: pointer;\n}\n\n.ItkVtkViewer-module_screenshotButton__OL4Na {\n flex: 1;\n width: 8mm;\n padding-top: 2px;\n padding-bottom: 2px;\n padding-left: 6px;\n padding-right: 6px;\n cursor: pointer;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_screenshotButton__OL4Na img {\n height: 1.2em;\n width: 1.2em;\n}\n\n.ItkVtkViewer-module_annotationsButton__Msb-p {\n flex: 1;\n width: 8mm;\n padding-top: 2px;\n padding-bottom: 2px;\n padding-left: 6px;\n padding-right: 6px;\n cursor: pointer;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_annotationsButton__Msb-p img {\n height: 1.2em;\n width: 1.2em;\n}\n\n.ItkVtkViewer-module_axesButton__k2H6p {\n flex: 1;\n width: 8mm;\n padding-top: 2px;\n padding-bottom: 2px;\n padding-left: 6px;\n padding-right: 6px;\n cursor: pointer;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_axesButton__k2H6p img {\n height: 1.2em;\n width: 1.2em;\n}\n\n.ItkVtkViewer-module_fullscreenButton__en3Z5 {\n flex: 1;\n width: 8m;\n padding-top: 2px;\n padding-bottom: 2px;\n padding-left: 6px;\n padding-right: 6px;\n cursor: pointer;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_fullscreenButton__en3Z5 img {\n height: 1.2em;\n width: 1.2em;\n}\n\n.ItkVtkViewer-module_interpolationButton__2P0HJ {\n flex: 1;\n width: 8mm;\n padding-top: 2px;\n padding-bottom: 2px;\n padding-left: 2px;\n padding-right: 4px;\n cursor: pointer;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_interpolationButton__2P0HJ img {\n width: 1.2em;\n}\n\n.ItkVtkViewer-module_cropButton__ljwuU {\n flex: 1;\n height: 8mm;\n padding-top: 2px;\n padding-bottom: 2px;\n padding-left: 6px;\n padding-right: 6px;\n cursor: pointer;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_cropButton__ljwuU img {\n height: 1.2em;\n width: 1.2em;\n}\n\n.ItkVtkViewer-module_resetCropButton__SCGTH {\n flex: 1;\n width: 8mm;\n padding-top: 2px;\n padding-bottom: 2px;\n padding-left: 6px;\n padding-right: 6px;\n cursor: pointer;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_resetCropButton__SCGTH img {\n height: 1.2em;\n width: 1.2em;\n}\n\n.ItkVtkViewer-module_distanceEntry__zXMUS {\n flex: 1;\n display: flex;\n flex-direction: row;\n align-items: self-start;\n}\n\n.ItkVtkViewer-module_distanceButton__NhxBT {\n flex: 1;\n height: 8mm;\n padding-top: 2px;\n padding-bottom: 2px;\n padding-left: 6px;\n padding-right: 6px;\n cursor: pointer;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_distanceButton__NhxBT img {\n height: 1.2em;\n width: 1.2em;\n}\n\n.ItkVtkViewer-module_distanceLabelCommon__Ec-uc {\n border: none;\n background: transparent;\n font-size: 1.2em;\n margin-right: 10px;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_distanceLabelBrightBG__aYmfG {\n color: black;\n}\n\n.ItkVtkViewer-module_distanceLabelDarkBG__kYXvI {\n color: white;\n}\n\n.ItkVtkViewer-module_distanceInput__gyNaU {\n background: transparent;\n color: white;\n font-size: 1em;\n width: 80px;\n}\n\n.ItkVtkViewer-module_resetCameraButton__l9FGp {\n flex: 1;\n width: 8mm;\n padding-top: 2px;\n padding-bottom: 2px;\n padding-left: 6px;\n padding-right: 6px;\n cursor: pointer;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_resetCameraButton__l9FGp img {\n height: 1.2em;\n width: 1.2em;\n}\n\n.ItkVtkViewer-module_bgColorButton__yrjOX {\n flex: 1;\n width: 8mm;\n padding-top: 2px;\n padding-bottom: 2px;\n padding-left: 6px;\n padding-right: 6px;\n cursor: pointer;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_bgColorButton__yrjOX img {\n height: 1.2em;\n width: 1.2em;\n}\n\n.ItkVtkViewer-module_viewModeButton__OtTng {\n flex: 1;\n width: 8mm;\n padding-top: 2px;\n padding-bottom: 2px;\n padding-left: 6px;\n padding-right: 6px;\n cursor: pointer;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_viewModeButton__OtTng img {\n width: 1.3em;\n height: 1.3em;\n}\n\n.ItkVtkViewer-module_shadowButton__09fEk {\n width: 8mm;\n padding: 4px;\n padding-left: 0px;\n cursor: pointer;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_shadowButton__09fEk img {\n width: 1.3em;\n height: 1.3em;\n}\n\n.ItkVtkViewer-module_viewPlanesButton__rSnuZ {\n flex: 1;\n width: 8mm;\n padding-top: 2px;\n padding-bottom: 2px;\n padding-left: 0px;\n padding-right: 6px;\n cursor: pointer;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_viewPlanesButton__rSnuZ img {\n width: 1.3em;\n height: 1.3em;\n}\n\n.ItkVtkViewer-module_toggleInput__jHLTo {\n margin: 0px;\n width: 0;\n opacity: 0;\n box-sizing: content-box;\n}\n\n.ItkVtkViewer-module_toggleButton__qHhHZ {\n cursor: pointer;\n border-radius: 0.2em;\n opacity: 0.45;\n}\n\ninput:checked.ItkVtkViewer-module_toggleInput__jHLTo + label {\n opacity: 1;\n}\n\n.ItkVtkViewer-module_numberInput__pDxYH {\n color: white;\n background: transparent;\n font-size: 1em;\n padding-left: 2px;\n width: 70px;\n}\n\n.ItkVtkViewer-module_selector__yw8l- {\n display: flex;\n direction: row;\n font-size: 1.2em;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_componentTab__6KSJF {\n position: absolute;\n opacity: 0;\n pointer-events: none;\n}\n\n.ItkVtkViewer-module_disableInterface__CGB4S {\n pointer-events: none;\n opacity: 0.5;\n}\n\n.ItkVtkViewer-module_componentTab__6KSJF + .ItkVtkViewer-module_compTabLabel__8u4iU {\n background: rgba(40, 40, 40, 0.5);\n padding: 5px;\n margin-right: 2px;\n border-radius: 5px 5px 0px 0px;\n color: #777;\n}\n\n.ItkVtkViewer-module_componentTab__6KSJF:hover + .ItkVtkViewer-module_compTabLabel__8u4iU {\n background: rgba(90, 90, 90, 0.5);\n}\n\n.ItkVtkViewer-module_componentTab__6KSJF:checked + .ItkVtkViewer-module_compTabLabel__8u4iU {\n background: rgba(127, 127, 127, 0.5);\n color: #fff;\n}\n\n.ItkVtkViewer-module_componentVisibility__y1rRS {\n position: relative;\n top: -2px;\n margin-left: 10px;\n}\n\nselect {\n -moz-appearance: none;\n}\n\nselect option {\n color: black;\n}\n\nselect:focus {\n outline: none;\n border: none;\n}\n\n.ItkVtkViewer-module_sampleDistanceButton__NjT0o {\n width: 8mm;\n padding: 4px;\n padding-left: 6px;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_sampleDistanceButton__NjT0o img {\n width: 1.2em;\n height: 1.2em;\n}\n\n.ItkVtkViewer-module_sliderColumn__ZwISb {\n display: flex;\n flex-direction: column;\n flex: 1;\n padding: 0 5px;\n}\n\n.ItkVtkViewer-module_sliderIcon__jfoL- {\n width: 1.8em;\n margin-right: 10px;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_blendModeButton__cit1w {\n width: 8mm;\n padding: 4px;\n padding-left: 8px;\n padding-right: 0px;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_blendModeButton__cit1w img {\n width: 1.2em;\n height: 1.2em;\n}\n\n.ItkVtkViewer-module_gradientOpacitySlider__wkEqP {\n width: 8mm;\n padding: 4px;\n padding-left: 6px;\n padding-right: 0px;\n z-index: 1000;\n}\n\n.ItkVtkViewer-module_gradientOpacitySlider__wkEqP img {\n width: 1.2em;\n height: 1.2em;\n}\n\n.ItkVtkViewer-module_sliderEntry__3r3gO {\n flex: 1;\n display: flex;\n flex-direction: row;\n align-items: center;\n}\n\n.ItkVtkViewer-module_slider__eT9qm {\n flex: 1;\n min-height: 1rem;\n}\n\n.ItkVtkViewer-module_planeLabel__E1zOk {\n padding-left: 6px;\n padding: 2px;\n display: block;\n font-size: 1.1em;\n font-family: monospace;\n color: black;\n border-width: 2px;\n border-radius: 10%;\n}\n\n.ItkVtkViewer-module_xPlaneLabel__wK4Cb {\n background-color: #ef5350;\n}\n\n.ItkVtkViewer-module_yPlaneLabel__rIm0j {\n background-color: #fdd835;\n}\n\n.ItkVtkViewer-module_zPlaneLabel__94NL7 {\n background-color: #4caf50;\n}\n\n.ItkVtkViewer-module_gradientOpacityScale__NrqOZ {\n z-index: 1100;\n position: relative;\n}\n\n.ItkVtkViewer-module_gradientOpacityScale__NrqOZ input {\n position: absolute;\n bottom: 20px;\n left: -24px;\n width: 12px;\n -ms-writing-mode: bt-lr;\n writing-mode: bt-lr;\n -webkit-appearance: slider-vertical;\n}\n\n.ItkVtkViewer-module_bigFileDrop__cZdkP {\n position: absolute;\n left: 50%;\n top: 50%;\n transform: translate(-50%, -50%);\n background-color: white;\n background-image: url('./dropBG.jpg');\n background-repeat: no-repeat;\n background-position: center;\n background-size: contain;\n border-radius: 10px;\n width: 50px;\n padding: calc(50vh - 2em) calc(50vw - 25px - 2em);\n}\n\n.ItkVtkViewer-module_fullscreenContainer__-H3c8 {\n position: absolute;\n width: 100vw;\n height: 100vh;\n top: 0;\n left: 0;\n overflow: hidden;\n background: black;\n margin: 0;\n padding: 0;\n}\n" var style = { loading: 'ItkVtkViewer-module_loading__11c63', spin: 'ItkVtkViewer-module_spin__mT5S6', @@ -55,6 +55,8 @@ var style = { layerLabelDarkBG: 'ItkVtkViewer-module_layerLabelDarkBG__sM6Bg', visibleButton: 'ItkVtkViewer-module_visibleButton__ezrIc', layerIcon: 'ItkVtkViewer-module_layerIcon__v-rxO', + iconGroup: 'ItkVtkViewer-module_iconGroup__qqZrW', + ldsRing: 'ItkVtkViewer-module_ldsRing__QT1wT', tooltipButtonBrightBG: 'ItkVtkViewer-module_tooltipButtonBrightBG__yffVf', tooltipButtonDarkBG: 'ItkVtkViewer-module_tooltipButtonDarkBG__gEu0i', invertibleButtonBrightBG: @@ -2049,7 +2051,7 @@ var mainUIMachineOptions = { }, } -function addLayerUIRow$1(context) { +function addLayerUIRow(context) { var layersUIRow = document.createElement('div') layersUIRow.setAttribute('class', style.layersUIRow) context.layers.layersUIGroup.appendChild(layersUIRow) @@ -2062,7 +2064,7 @@ function createLayersInterface(context) { context.uiGroups.set('layers', layersUIGroup) // layer name -> layerEntry map context.layers.uiLayers = new Map() - addLayerUIRow$1(context) + addLayerUIRow(context) context.uiContainer.appendChild(layersUIGroup) } @@ -2082,7 +2084,6 @@ function createLayerEntry(context, name, layer) { .concat(style.toggleButton, '" for="') .concat(context.id, '-visibleButton">') - visibleButton.children[0] var visibleLabel = visibleButton.children[1] applyContrastSensitiveStyleToElement( context, @@ -2101,7 +2102,6 @@ function createLayerEntry(context, name, layer) { .concat(style.toggleButton, '" for="') .concat(context.id, '-invisibleButton">') - invisibleButton.children[0] var invisibleLabel = invisibleButton.children[1] applyContrastSensitiveStyleToElement( context, @@ -2141,6 +2141,14 @@ function createLayerEntry(context, name, layer) { applyContrastSensitiveStyleToElement(context, 'layerLabel', layerLabel) layerLabel.innerText = name layerEntry.appendChild(layerLabel) + var imageIcons = document.createElement('div') + imageIcons.setAttribute('class', ''.concat(style.iconGroup)) + layerEntry.appendChild(imageIcons) + var spinner = document.createElement('div') + spinner.setAttribute('class', ''.concat(style.ldsRing)) + spinner.innerHTML = '
' + imageIcons.appendChild(spinner) + layer.spinner = spinner var iconElement = document.createElement('div') switch (layer.type) { @@ -2166,7 +2174,7 @@ function createLayerEntry(context, name, layer) { iconElement.setAttribute('class', style.layerIcon) applyContrastSensitiveStyleToElement(context, 'invertibleButton', iconElement) - layerEntry.appendChild(iconElement) + imageIcons.appendChild(iconElement) layerEntry.addEventListener('click', function(event) { event.preventDefault() context.service.send({ @@ -2177,7 +2185,7 @@ function createLayerEntry(context, name, layer) { return layerEntry } -function createLayerInterface(context, event) { +function createLayerInterface(context) { var name = context.layers.lastAddedData.name var layer = context.layers.actorContext.get(name) var layersUIGroup = context.layers.layersUIGroup @@ -2193,14 +2201,6 @@ function createLayerInterface(context, event) { } } - if (!!!layerEntry) { - addLayerUIRow(context) - var _uiRow = layersUIGroup[layersUIGroup.children.length - 1] - layerEntry = createLayerEntry(context, name, layer) - - _uiRow.appendChild(layerEntry) - } - context.layers.uiLayers.set(name, layerEntry) } @@ -2313,12 +2313,23 @@ function selectLayer(context, event) { } } +function startDataUpdate(_ref) { + var spinner = _ref.actorContext.spinner + spinner.style.visibility = 'visible' +} +function finishDataUpdate(_ref2) { + var spinner = _ref2.actorContext.spinner + spinner.style.visibility = 'hidden' +} + var layersUIMachineOptions = { layerUIActor: { actions: { createLayerInterface: createLayerInterface, selectLayer: selectLayer, toggleLayerVisibility: toggleLayerVisibility, + startDataUpdate: startDataUpdate, + finishDataUpdate: finishDataUpdate, }, }, actions: { @@ -17831,12 +17842,10 @@ function updateRenderedImageInterface(context, event) { var points = actorContext.piecewiseFunctionPoints.get( actorContext.selectedComponent - ) + ) // no points if just label image if (points) { transferFunctionWidget.setPoints(points) - } else { - console.warn('No transfer function points for component') } } diff --git a/src/UI/reference-ui/src/Images/updateRenderedImageInterface.js b/src/UI/reference-ui/src/Images/updateRenderedImageInterface.js index ca6bd3af5..0f36b3656 100644 --- a/src/UI/reference-ui/src/Images/updateRenderedImageInterface.js +++ b/src/UI/reference-ui/src/Images/updateRenderedImageInterface.js @@ -11,10 +11,9 @@ function updateRenderedImageInterface(context, event) { const points = actorContext.piecewiseFunctionPoints.get( actorContext.selectedComponent ) + // no points if just label image if (points) { transferFunctionWidget.setPoints(points) - } else { - console.warn('No transfer function points for component') } } diff --git a/src/UI/reference-ui/src/ItkVtkViewer.module.css b/src/UI/reference-ui/src/ItkVtkViewer.module.css index be6dd251c..adf22c99b 100644 --- a/src/UI/reference-ui/src/ItkVtkViewer.module.css +++ b/src/UI/reference-ui/src/ItkVtkViewer.module.css @@ -37,7 +37,7 @@ min-height: 0; } -.uiContainer { +.uiContainer { position: absolute; top: 0; left: 0; @@ -242,7 +242,6 @@ justify-content: space-between; border-style: solid; border-width: 2px; - border-radius: 10%; } .layerEntryBrightBG { @@ -284,6 +283,10 @@ padding-right: 6px; } +.layerIcon { + display: inline-block; +} + .layerIcon img { height: 1.2em; width: 1.2em; @@ -293,6 +296,46 @@ padding-right: 6px; } +.iconGroup { + display: inline-block; +} + +.ldsRing { + display: inline-block; + position: relative; + width: 20px; + height: 20px; +} +.ldsRing div { + box-sizing: border-box; + display: block; + position: absolute; + width: 1em; + height: 1em; + margin: 0; + border: 0.15em solid #000; + border-radius: 50%; + animation: ldsRing 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; + border-color: #000 transparent transparent transparent; +} +.ldsRing div:nth-child(1) { + animation-delay: -0.45s; +} +.ldsRing div:nth-child(2) { + animation-delay: -0.3s; +} +.ldsRing div:nth-child(3) { + animation-delay: -0.15s; +} +@keyframes ldsRing { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + .tooltipButtonBrightBG::before { } diff --git a/src/UI/reference-ui/src/Layers/createLayerInterface.js b/src/UI/reference-ui/src/Layers/createLayerInterface.js index 4b537d200..1144ba9a9 100644 --- a/src/UI/reference-ui/src/Layers/createLayerInterface.js +++ b/src/UI/reference-ui/src/Layers/createLayerInterface.js @@ -16,7 +16,6 @@ function createLayerEntry(context, name, layer) { const visibleButton = document.createElement('div') visibleButton.innerHTML = `` - const visibleButtonInput = visibleButton.children[0] const visibleLabel = visibleButton.children[1] applyContrastSensitiveStyleToElement( context, @@ -26,7 +25,6 @@ function createLayerEntry(context, name, layer) { layerEntry.appendChild(visibleButton) const invisibleButton = document.createElement('div') invisibleButton.innerHTML = `` - const invisibleButtonInput = invisibleButton.children[0] const invisibleLabel = invisibleButton.children[1] applyContrastSensitiveStyleToElement( context, @@ -62,6 +60,17 @@ function createLayerEntry(context, name, layer) { layerLabel.innerText = name layerEntry.appendChild(layerLabel) + const imageIcons = document.createElement('div') + imageIcons.setAttribute('class', `${style.iconGroup}`) + layerEntry.appendChild(imageIcons) + + const spinner = document.createElement('div') + spinner.setAttribute('class', `${style.ldsRing}`) + spinner.innerHTML = '' + imageIcons.appendChild(spinner) + + layer.spinner = spinner + const iconElement = document.createElement('div') switch (layer.type) { case 'image': { @@ -77,7 +86,7 @@ function createLayerEntry(context, name, layer) { } iconElement.setAttribute('class', style.layerIcon) applyContrastSensitiveStyleToElement(context, 'invertibleButton', iconElement) - layerEntry.appendChild(iconElement) + imageIcons.appendChild(iconElement) layerEntry.addEventListener('click', event => { event.preventDefault() @@ -87,7 +96,7 @@ function createLayerEntry(context, name, layer) { return layerEntry } -function createLayerInterface(context, event) { +function createLayerInterface(context) { const name = context.layers.lastAddedData.name const layer = context.layers.actorContext.get(name) const layersUIGroup = context.layers.layersUIGroup @@ -101,12 +110,6 @@ function createLayerInterface(context, event) { uiRow.appendChild(layerEntry) } } - if (!!!layerEntry) { - addLayerUIRow(context) - const uiRow = layersUIGroup[layersUIGroup.children.length - 1] - layerEntry = createLayerEntry(context, name, layer) - uiRow.appendChild(layerEntry) - } context.layers.uiLayers.set(name, layerEntry) } diff --git a/src/UI/reference-ui/src/Layers/dataUpdateIndicator.js b/src/UI/reference-ui/src/Layers/dataUpdateIndicator.js new file mode 100644 index 000000000..a522bb83a --- /dev/null +++ b/src/UI/reference-ui/src/Layers/dataUpdateIndicator.js @@ -0,0 +1,7 @@ +export function startDataUpdate({ actorContext: { spinner } }) { + spinner.style.visibility = 'visible' +} + +export function finishDataUpdate({ actorContext: { spinner } }) { + spinner.style.visibility = 'hidden' +} diff --git a/src/UI/reference-ui/src/Layers/layersUIMachineOptions.js b/src/UI/reference-ui/src/Layers/layersUIMachineOptions.js index 094f901f9..ea37066b7 100644 --- a/src/UI/reference-ui/src/Layers/layersUIMachineOptions.js +++ b/src/UI/reference-ui/src/Layers/layersUIMachineOptions.js @@ -2,15 +2,16 @@ import createLayersInterface from './createLayersInterface' import createLayerInterface from './createLayerInterface' import toggleLayerVisibility from './toggleLayerVisibility' import selectLayer from './selectLayer' +import { startDataUpdate, finishDataUpdate } from './dataUpdateIndicator' const layersUIMachineOptions = { layerUIActor: { actions: { createLayerInterface, - selectLayer, - toggleLayerVisibility, + startDataUpdate, + finishDataUpdate, }, }, diff --git a/src/createViewerMachine.js b/src/createViewerMachine.js index bebe1ab23..8fc5701bb 100644 --- a/src/createViewerMachine.js +++ b/src/createViewerMachine.js @@ -164,6 +164,9 @@ const createViewerMachine = (options, context, eventEmitterCallback) => { UPDATE_RENDERED_IMAGE: { actions: [forwardTo('rendering')], }, + START_DATA_UPDATE: { actions: forwardTo('ui') }, + FINISH_DATA_UPDATE: { actions: forwardTo('ui') }, + POST_RENDER: { actions: forwardTo('ui') }, RENDERED_IMAGE_ASSIGNED: { actions: [forwardTo('ui'), forwardTo('eventEmitter')], },