From 6f6a4f36a1c52958b59a48ca69b13953e82ee945 Mon Sep 17 00:00:00 2001 From: Julien Finet Date: Sat, 13 Jan 2024 20:11:54 +0100 Subject: [PATCH] docs(imageviewer): add an ImageViewer example This can help with debugging. --- .../ImageViewer/ImageViewer.module.css | 131 ++++++ .../ImageViewer/controlPanel.html | 0 Examples/Applications/ImageViewer/dropBG.jpg | Bin 0 -> 33972 bytes Examples/Applications/ImageViewer/dropBG.svg | 73 ++++ Examples/Applications/ImageViewer/index.js | 399 ++++++++++++++++++ Examples/Applications/ImageViewer/index.md | 13 + 6 files changed, 616 insertions(+) create mode 100644 Examples/Applications/ImageViewer/ImageViewer.module.css create mode 100644 Examples/Applications/ImageViewer/controlPanel.html create mode 100644 Examples/Applications/ImageViewer/dropBG.jpg create mode 100644 Examples/Applications/ImageViewer/dropBG.svg create mode 100644 Examples/Applications/ImageViewer/index.js create mode 100644 Examples/Applications/ImageViewer/index.md diff --git a/Examples/Applications/ImageViewer/ImageViewer.module.css b/Examples/Applications/ImageViewer/ImageViewer.module.css new file mode 100644 index 00000000000..fe99e4914ed --- /dev/null +++ b/Examples/Applications/ImageViewer/ImageViewer.module.css @@ -0,0 +1,131 @@ +body { + display: flex; + align-items: center; + justify-content: center; +} + +.control { + display: flex; + flex-direction: column; + flex: 1; + align-items: flex-start; + gap: 10px; +} + +.rootController { + display: flex; + flex-direction: row; + position: absolute; + top: 5px; + left: 5px; + right: 5px; + z-index: 1; +} + +label { + color: white; + padding:5px; + background-color:gray; + color:#ffffff; + border-radius: 2px; + padding: 2px; +} + +.fullScreen { + position: absolute; + width: 100vw; + height: 100vh; + top: 0; + left: 0; + overflow: hidden; + background: black; + margin: 0; + padding: 0; +} + +.fullParentSize { + position: absolute; + width: 100%; + height: 100%; + top: 0; + left: 0; + overflow: hidden; +} + +.bigFileDrop { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + background-color: white; + background-image: url('./dropBG.jpg'); + background-repeat: no-repeat; + background-position: center; + background-size: contain; + border-radius: 10px; + width: 50px; + padding: calc(50vh - 2em) calc(50vw - 25px - 2em); + cursor: pointer; +} + +.selector { + position: absolute; + top: 5px; + left: 400px; + transform: translate(-100%, 0px); + border: none; + background: transparent; + color: white; + border: none; +} + +select { + -moz-appearance: none; +} + +select option { + color: black; +} + +select:focus { + outline: none; + border: none; +} + +.piecewiseWidget { + position: absolute; + top: calc(10px + 1em); + left: 5px; + background: rgba(255, 255, 255, 0.3); + border-radius: 5px; +} + +.shadow { + position: absolute; + top: 5px; + left: 5px; + border: none; + background: transparent; + color: white; + border: none; +} + +.progress { + flex: none; + font-size: 50px; + color: black; + z-index: 1; + background: rgba(128,128,128,.5); + padding: 20px; + border-radius: 10px; + user-select: none; +} + +.fpsMonitor { + position: absolute; + bottom: 10px; + left: 10px; + background-color: rgba(255, 255, 255, 0.5); + border-radius: 5px; + border: solid 1px gray; +} diff --git a/Examples/Applications/ImageViewer/controlPanel.html b/Examples/Applications/ImageViewer/controlPanel.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/Examples/Applications/ImageViewer/dropBG.jpg b/Examples/Applications/ImageViewer/dropBG.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fc2a2b89cce9d5bc2aa791f87941756b07e1c942 GIT binary patch literal 33972 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

D^8TC(x;xM_x?esAto;<7q5vVMXyIiJ19JzI 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 js;P7|S*BF + + + imageViewer + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Image + Viewer + + + Drop + vti file + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Examples/Applications/ImageViewer/index.js b/Examples/Applications/ImageViewer/index.js new file mode 100644 index 00000000000..ddd2d37543c --- /dev/null +++ b/Examples/Applications/ImageViewer/index.js @@ -0,0 +1,399 @@ +/* eslint-disable import/prefer-default-export */ +/* eslint-disable import/no-extraneous-dependencies */ + +import '@kitware/vtk.js/favicon'; + +// Load the rendering pieces we want to use (for both WebGL and WebGPU) +import '@kitware/vtk.js/Rendering/Profiles/Volume'; +import '@kitware/vtk.js/Rendering/Profiles/Geometry'; + +import macro from '@kitware/vtk.js/macros'; +import HttpDataAccessHelper from '@kitware/vtk.js/IO/Core/DataAccessHelper/HttpDataAccessHelper'; +import vtkBoundingBox from '@kitware/vtk.js/Common/DataModel/BoundingBox'; +import vtkColorTransferFunction from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction'; +import vtkColorMaps from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction/ColorMaps'; +import vtkInteractorStyleImage from '@kitware/vtk.js/Interaction/Style/InteractorStyleImage'; +import vtkFullScreenRenderWindow from '@kitware/vtk.js/Rendering/Misc/FullScreenRenderWindow'; +import vtkPiecewiseFunction from '@kitware/vtk.js/Common/DataModel/PiecewiseFunction'; +import vtkURLExtract from '@kitware/vtk.js/Common/Core/URLExtract'; +import vtkImageSlice from '@kitware/vtk.js/Rendering/Core/ImageSlice'; +import vtkImageMapper from '@kitware/vtk.js/Rendering/Core/ImageMapper'; +import Constants from '@kitware/vtk.js/Rendering/Core/ImageMapper/Constants'; +import vtkXMLImageDataReader from '@kitware/vtk.js/IO/XML/XMLImageDataReader'; +import vtkFPSMonitor from '@kitware/vtk.js/Interaction/UI/FPSMonitor'; +import vtkScalarBarActor from 'vtk.js/Sources/Rendering/Core/ScalarBarActor'; + +// Force DataAccessHelper to have access to various data source +import '@kitware/vtk.js/IO/Core/DataAccessHelper/HtmlDataAccessHelper'; +import '@kitware/vtk.js/IO/Core/DataAccessHelper/JSZipDataAccessHelper'; + +import style from './ImageViewer.module.css'; +import icon from '../../../Documentation/content/icon/favicon-96x96.png'; + +const { SlicingMode } = Constants; + +// Process arguments from URL +const userParams = vtkURLExtract.extractURLParameters(); + +let autoInit = true; +let background = [0, 0, 0]; +const lutName = userParams.lut || 'Grayscale'; +const lookupTable = vtkColorTransferFunction.newInstance(); + +// Background handling +if (userParams.background) { + background = userParams.background.split(',').map((s) => Number(s)); +} +const selectorClass = + background.length === 3 && background.reduce((a, b) => a + b, 0) < 1.5 + ? style.dark + : style.light; + +// ---------------------------------------------------------------------------- +// DOM containers for UI control +// ---------------------------------------------------------------------------- + +const rootControllerContainer = document.createElement('div'); +rootControllerContainer.setAttribute('class', style.rootController); + +const addDataSetButton = document.createElement('img'); +addDataSetButton.setAttribute('class', style.button); +addDataSetButton.setAttribute('src', icon); +addDataSetButton.addEventListener('click', () => { + const isVisible = rootControllerContainer.style.display !== 'none'; + rootControllerContainer.style.display = isVisible ? 'none' : 'flex'; +}); + +const fpsMonitor = vtkFPSMonitor.newInstance(); +const fpsElm = fpsMonitor.getFpsMonitorContainer(); +fpsElm.classList.add(style.fpsMonitor); + +const iOS = /iPad|iPhone|iPod/.test(window.navigator.platform); + +if (iOS) { + document.querySelector('body').classList.add('is-ios-device'); +} + +// ---------------------------------------------------------------------------- + +function emptyContainer(container) { + while (container.firstChild) { + container.removeChild(container.firstChild); + } +} + +// ---------------------------------------------------------------------------- + +function preventDefaults(e) { + e.preventDefault(); + e.stopPropagation(); +} + +function createUI(renderWindow, interactorStyle, imageSlice) { + const image = imageSlice.getMapper().getInputData(); + const scalars = image.getPointData().getScalars(); + const info = document.createElement('label'); + info.innerText = `w: ${image.getDimensions()[0]}px h: ${ + image.getDimensions()[1] + }px d: ${image.getDimensions()[2]}px + ${scalars.getDataType()} min: ${scalars.getRange()[0]} max: ${ + scalars.getRange()[1] + }`; + + // -------------------------------------------------------------------- + // Color handling + // -------------------------------------------------------------------- + + const presetSelector = document.createElement('select'); + presetSelector.setAttribute('class', selectorClass); + presetSelector.innerHTML = vtkColorMaps.rgbPresetNames + .map( + (name) => + `` + ) + .join(''); + + function applyPreset() { + const preset = vtkColorMaps.getPresetByName(presetSelector.value); + lookupTable.applyColorMap(preset); + lookupTable.setMappingRange(...scalars.getRange()); + lookupTable.updateRange(); + renderWindow.getInteractor().render(); + } + applyPreset(); + presetSelector.addEventListener('change', applyPreset); + + const windowLabel = document.createElement('label'); + windowLabel.for = 'window'; + windowLabel.innerText = 'Window:'; + const windowSelector = document.createElement('input'); + windowSelector.setAttribute('class', selectorClass); + windowSelector.setAttribute('id', 'window'); + windowSelector.type = 'number'; + + const levelLabel = document.createElement('label'); + levelLabel.for = 'level'; + levelLabel.innerText = 'Level:'; + const levelSelector = document.createElement('input'); + levelSelector.setAttribute('class', selectorClass); + levelSelector.setAttribute('id', 'level'); + levelSelector.type = 'number'; + const windowLevel = document.createElement('div'); + windowLevel.appendChild(windowLabel); + windowLevel.appendChild(windowSelector); + windowLevel.appendChild(levelLabel); + windowLevel.appendChild(levelSelector); + + function updateWindowLevelSelectors() { + windowSelector.value = imageSlice.getProperty().getColorWindow(); + levelSelector.value = imageSlice.getProperty().getColorLevel(); + } + updateWindowLevelSelectors(); + interactorStyle.onInteractionEvent(updateWindowLevelSelectors); + + function updateWindowLevel() { + imageSlice.getProperty().setColorWindow(Number(windowSelector.value)); + imageSlice.getProperty().setColorLevel(Number(levelSelector.value)); + renderWindow.getInteractor().render(); + } + windowSelector.addEventListener('input', updateWindowLevel); + levelSelector.addEventListener('input', updateWindowLevel); + + const interpolationLabel = document.createElement('label'); + interpolationLabel.for = 'interpolation'; + interpolationLabel.innerText = 'Linear interpolation:'; + const interpolationSelector = document.createElement('input'); + interpolationSelector.setAttribute('class', selectorClass); + interpolationSelector.setAttribute('id', 'interpolation'); + interpolationSelector.type = 'checkbox'; + interpolationSelector.checked = true; + const interpolation = document.createElement('div'); + interpolation.appendChild(interpolationLabel); + interpolation.appendChild(interpolationSelector); + + function updateInterpolation() { + if (interpolationSelector.checked) { + imageSlice.getProperty().setInterpolationTypeToLinear(); + } else { + imageSlice.getProperty().setInterpolationTypeToNearest(); + } + renderWindow.getInteractor().render(); + } + interpolationSelector.addEventListener('input', updateInterpolation); + + const controlContainer = document.createElement('div'); + controlContainer.setAttribute('class', style.control); + controlContainer.appendChild(info); + controlContainer.appendChild(presetSelector); + controlContainer.appendChild(windowLevel); + controlContainer.appendChild(interpolation); + rootControllerContainer.appendChild(controlContainer); +} +// ---------------------------------------------------------------------------- + +function createViewer(rootContainer, fileContents, options) { + const rwBackground = options.background + ? options.background.split(',').map((s) => Number(s)) + : background; + const containerStyle = options.containerStyle; + const fullScreenRenderer = vtkFullScreenRenderWindow.newInstance({ + background: rwBackground, + rootContainer, + containerStyle, + }); + const renderer = fullScreenRenderer.getRenderer(); + const renderWindow = fullScreenRenderer.getRenderWindow(); + renderWindow.getInteractor().setDesiredUpdateRate(30); + + const vtiReader = vtkXMLImageDataReader.newInstance(); + vtiReader.parseAsArrayBuffer(fileContents); + + const source = vtiReader.getOutputData(0); + const mapper = vtkImageMapper.newInstance(); + const actor = vtkImageSlice.newInstance(); + + const piecewiseFunction = vtkPiecewiseFunction.newInstance(); + + // Pipeline handling + actor.setMapper(mapper); + mapper.setInputData(source); + mapper.setSliceAtFocalPoint(true); + mapper.setSlicingMode(SlicingMode.Z); + renderer.addActor(actor); + + // Configuration + // Uncomment this if you want to use a fixed colorwindow/level + actor.getProperty().setRGBTransferFunction(0, lookupTable); + // actor.getProperty().setScalarOpacity(0, piecewiseFunction); + + const bounds = source.getBounds(); + const camera = renderer.getActiveCamera(); + + camera.setFocalPoint(...vtkBoundingBox.getCenter(bounds)); + const position = camera.getFocalPoint(); + // offset along the slicing axis + const normal = mapper.getSlicingModeNormal(); + position[0] += normal[0]; + position[1] += normal[1]; + position[2] += normal[2]; + camera.setPosition(...position); + switch (mapper.getSlicingMode()) { + case SlicingMode.X: + camera.setViewUp([0, 1, 0]); + camera.setParallelScale((bounds[1] - bounds[0]) / 2); + break; + case SlicingMode.Y: + camera.setViewUp([1, 0, 0]); + camera.setParallelScale((bounds[5] - bounds[4]) / 2); + break; + case SlicingMode.Z: + camera.setViewUp([0, 1, 0]); + camera.setParallelScale((bounds[1] - bounds[0]) / 2); + break; + default: + } + camera.setParallelProjection(true); + + const scalarBarActor = vtkScalarBarActor.newInstance(); + scalarBarActor.setAxisLabel('foo'); + // scalarBarActor.setBoxPosition(-1.0, -0.5); + // scalarBarActor.setBoxSize(2.0, 1.0); + scalarBarActor.setVisibility(true); + renderer.addActor(scalarBarActor); + scalarBarActor.setScalarsToColors( + actor.getProperty().getRGBTransferFunction() + ); + + const iStyle = vtkInteractorStyleImage.newInstance(); + iStyle.setInteractionMode('IMAGE_SLICING'); + renderWindow.getInteractor().setInteractorStyle(iStyle); + + createUI(renderWindow, iStyle, actor); + rootContainer.appendChild(rootControllerContainer); + + // First render + // renderer.resetCamera(); + renderWindow.render(); + + global.pipeline = { + actor, + renderer, + renderWindow, + lookupTable, + mapper, + source, + piecewiseFunction, + fullScreenRenderer, + scalarBarActor, + }; + + if (userParams.fps) { + fpsElm.classList.add(style.fpsMonitor); + fpsMonitor.setRenderWindow(renderWindow); + fpsMonitor.setContainer(rootContainer); + fpsMonitor.update(); + } +} + +// ---------------------------------------------------------------------------- + +export function load(container, options) { + autoInit = false; + emptyContainer(container); + + if (options.file) { + if (options.ext === 'vti') { + const reader = new FileReader(); + reader.onload = function onLoad(e) { + createViewer(container, reader.result, options); + }; + reader.readAsArrayBuffer(options.file); + } else { + console.error('Unkown file...'); + } + } else if (options.fileURL) { + const progressContainer = document.createElement('div'); + progressContainer.setAttribute('class', style.progress); + container.appendChild(progressContainer); + + const progressCallback = (progressEvent) => { + if (progressEvent.lengthComputable) { + const percent = Math.floor( + (100 * progressEvent.loaded) / progressEvent.total + ); + progressContainer.innerHTML = `Loading ${percent}%`; + } else { + progressContainer.innerHTML = macro.formatBytesToProperUnit( + progressEvent.loaded + ); + } + }; + + HttpDataAccessHelper.fetchBinary(options.fileURL, { + progressCallback, + }).then((binary) => { + container.removeChild(progressContainer); + createViewer(container, binary, options); + }); + } +} + +export function initLocalFileLoader(container) { + const exampleContainer = document.querySelector('.content'); + const rootBody = document.querySelector('body'); + const myContainer = container || exampleContainer || rootBody; + + const fileContainer = document.createElement('div'); + fileContainer.innerHTML = `

`; + myContainer.appendChild(fileContainer); + + const fileInput = fileContainer.querySelector('input'); + + function handleFile(e) { + preventDefaults(e); + const dataTransfer = e.dataTransfer; + const files = e.target.files || dataTransfer.files; + if (files.length === 1) { + myContainer.removeChild(fileContainer); + const ext = files[0].name.split('.').slice(-1)[0]; + const options = { file: files[0], ext, ...userParams }; + load(myContainer, options); + } + } + + fileInput.addEventListener('change', handleFile); + fileContainer.addEventListener('drop', handleFile); + fileContainer.addEventListener('click', (e) => fileInput.click()); + fileContainer.addEventListener('dragover', preventDefaults); +} + +// Look at URL an see if we should load a file +// ?fileURL=https://data.kitware.com/api/v1/item/59cdbb588d777f31ac63de08/download +if (userParams.fileURL) { + const exampleContainer = document.querySelector('.content'); + const rootBody = document.querySelector('body'); + const myContainer = exampleContainer || rootBody; + load(myContainer, userParams); +} + +const viewerContainers = document.querySelectorAll('.vtkjs-image-viewer'); +let nbViewers = viewerContainers.length; +while (nbViewers--) { + const viewerContainer = viewerContainers[nbViewers]; + const fileURL = viewerContainer.dataset.url; + const options = { + containerStyle: { height: '100%' }, + ...userParams, + fileURL, + }; + load(viewerContainer, options); +} + +// Auto setup if no method get called within 100ms +setTimeout(() => { + if (autoInit) { + initLocalFileLoader(); + } +}, 100); diff --git a/Examples/Applications/ImageViewer/index.md b/Examples/Applications/ImageViewer/index.md new file mode 100644 index 00000000000..4c6cc9dd082 --- /dev/null +++ b/Examples/Applications/ImageViewer/index.md @@ -0,0 +1,13 @@ +## Image Viewer + +The Image Viewer is a standalone single page web application that can be used to visualize and explore VTI files. The only requirement is the single [HTML] file without any web server. This application can be use to load vti file and use image rendering to render them. +The current vti supported format is __ascii__, __binary__ and __binary+zlib__ compression. + +Some sample files can be found [here](https://data.kitware.com/#collection/586fef9f8d777f05f44a5c86/folder/59de9cf48d777f31ac641dbc) + +Also using extra argument to the URL allow to view remote VTI like the links below: +- [head-ascii.vti](https://kitware.github.io/vtk-js/examples/VolumeViewer/VolumeViewer.html?fileURL=https://data.kitware.com/api/v1/item/59de9d418d777f31ac641dbe/download) 30.31 MB +- [head-binary.vti](https://kitware.github.io/vtk-js/examples/VolumeViewer/VolumeViewer.html?fileURL=https://data.kitware.com/api/v1/item/59de9dc98d777f31ac641dc1/download) 15.67 MB +- [head-binary-zlib.vti](https://kitware.github.io/vtk-js/examples/VolumeViewer/VolumeViewer.html?fileURL=https://data.kitware.com/api/v1/item/59e12e988d777f31ac6455c5/download) 8.439 MB + +[HTML]: https://kitware.github.io/vtk-js/examples/VolumeViewer/VolumeViewer.html