import '@kitware/vtk.js/favicon';
import '@kitware/vtk.js/Rendering/Profiles/Volume';
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 vtkFullScreenRenderWindow from '@kitware/vtk.js/Rendering/Misc/FullScreenRenderWindow'; import vtkPiecewiseFunction from '@kitware/vtk.js/Common/DataModel/PiecewiseFunction'; import vtkVolumeController from '@kitware/vtk.js/Interaction/UI/VolumeController'; import vtkURLExtract from '@kitware/vtk.js/Common/Core/URLExtract'; import vtkVolume from '@kitware/vtk.js/Rendering/Core/Volume'; import vtkVolumeMapper from '@kitware/vtk.js/Rendering/Core/VolumeMapper'; import vtkXMLImageDataReader from '@kitware/vtk.js/IO/XML/XMLImageDataReader'; import vtkFPSMonitor from '@kitware/vtk.js/Interaction/UI/FPSMonitor';
import '@kitware/vtk.js/IO/Core/DataAccessHelper/HtmlDataAccessHelper'; import '@kitware/vtk.js/IO/Core/DataAccessHelper/JSZipDataAccessHelper';
import style from './VolumeViewer.module.css';
let autoInit = true; const userParams = vtkURLExtract.extractURLParameters(); const fpsMonitor = vtkFPSMonitor.newInstance();
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 createViewer(rootContainer, fileContents, options) { const background = options.background ? options.background.split(',').map((s) => Number(s)) : [0, 0, 0]; const containerStyle = options.containerStyle; const fullScreenRenderer = vtkFullScreenRenderWindow.newInstance({ background, 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 = vtkVolumeMapper.newInstance(); const actor = vtkVolume.newInstance();
const dataArray = source.getPointData().getScalars() || source.getPointData().getArrays()[0]; const dataRange = dataArray.getRange();
const lookupTable = vtkColorTransferFunction.newInstance(); const piecewiseFunction = vtkPiecewiseFunction.newInstance();
actor.setMapper(mapper); mapper.setInputData(source); renderer.addActor(actor);
const sampleDistance = 0.7 * Math.sqrt( source .getSpacing() .map((v) => v * v) .reduce((a, b) => a + b, 0) ); mapper.setSampleDistance(sampleDistance); actor.getProperty().setRGBTransferFunction(0, lookupTable); actor.getProperty().setScalarOpacity(0, piecewiseFunction); actor.getProperty().setInterpolationTypeToLinear();
actor .getProperty() .setScalarOpacityUnitDistance( 0, vtkBoundingBox.getDiagonalLength(source.getBounds()) / Math.max(...source.getDimensions()) ); actor.getProperty().setGradientOpacityMinimumValue(0, 0); actor .getProperty() .setGradientOpacityMaximumValue(0, (dataRange[1] - dataRange[0]) * 0.05); actor.getProperty().setShade(true); actor.getProperty().setUseGradientOpacity(0, true); actor.getProperty().setGradientOpacityMinimumOpacity(0, 0.0); actor.getProperty().setGradientOpacityMaximumOpacity(0, 1.0); actor.getProperty().setAmbient(0.2); actor.getProperty().setDiffuse(0.7); actor.getProperty().setSpecular(0.3); actor.getProperty().setSpecularPower(8.0);
const controllerWidget = vtkVolumeController.newInstance({ size: [400, 150], rescaleColorMap: true, }); const isBackgroundDark = background[0] + background[1] + background[2] < 1.5; controllerWidget.setContainer(rootContainer); controllerWidget.setupContent(renderWindow, actor, isBackgroundDark);
fullScreenRenderer.setResizeCallback(({ width, height }) => { if (width > 414) { controllerWidget.setSize(400, 150); } else { controllerWidget.setSize(width - 14, 150); } controllerWidget.render(); fpsMonitor.update(); });
renderer.resetCamera(); renderWindow.render();
global.pipeline = { actor, renderer, renderWindow, lookupTable, mapper, source, piecewiseFunction, fullScreenRenderer, };
if (userParams.fps) { const fpsElm = fpsMonitor.getFpsMonitorContainer(); 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 = `<div class="${style.bigFileDrop}"/><input type="file" accept=".vti" style="display: none;"/>`; 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); }
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-volume-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); }
setTimeout(() => { if (autoInit) { initLocalFileLoader(); } }, 100);
|