|
@@ -0,0 +1,304 @@
|
|
|
+/* eslint-disable import/prefer-default-export */
|
|
|
+/* eslint-disable import/no-extraneous-dependencies */
|
|
|
+
|
|
|
+import 'vtk.js/Sources/favicon';
|
|
|
+
|
|
|
+import macro from 'vtk.js/Sources/macro';
|
|
|
+import HttpDataAccessHelper from 'vtk.js/Sources/IO/Core/DataAccessHelper/HttpDataAccessHelper';
|
|
|
+import vtkBoundingBox from 'vtk.js/Sources/Common/DataModel/BoundingBox';
|
|
|
+import vtkColorTransferFunction from 'vtk.js/Sources/Rendering/Core/ColorTransferFunction';
|
|
|
+import vtkFullScreenRenderWindow from 'vtk.js/Sources/Rendering/Misc/FullScreenRenderWindow';
|
|
|
+import vtkPiecewiseFunction from 'vtk.js/Sources/Common/DataModel/PiecewiseFunction';
|
|
|
+import vtkVolumeController from 'vtk.js/Sources/Interaction/UI/VolumeController';
|
|
|
+import vtkURLExtract from 'vtk.js/Sources/Common/Core/URLExtract';
|
|
|
+import vtkVolume from 'vtk.js/Sources/Rendering/Core/Volume';
|
|
|
+import vtkVolumeMapper from 'vtk.js/Sources/Rendering/Core/VolumeMapper';
|
|
|
+import vtkXMLImageDataReader from 'vtk.js/Sources/IO/XML/XMLImageDataReader';
|
|
|
+import vtkFPSMonitor from 'vtk.js/Sources/Interaction/UI/FPSMonitor';
|
|
|
+import controlPanel from '/data/controller.html';
|
|
|
+import style from './VolumeViewer.module.css';
|
|
|
+
|
|
|
+
|
|
|
+let autoInit = true;
|
|
|
+const userParams = vtkURLExtract.extractURLParameters();
|
|
|
+const fpsMonitor = vtkFPSMonitor.newInstance();
|
|
|
+
|
|
|
+// ----------------------------------------------------------------------------
|
|
|
+// Add class to body if iOS device
|
|
|
+// ----------------------------------------------------------------------------
|
|
|
+
|
|
|
+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();
|
|
|
+}
|
|
|
+
|
|
|
+// ----------------------------------------------------------------------------
|
|
|
+
|
|
|
+const fullScreenRenderer = vtkFullScreenRenderWindow.newInstance({
|
|
|
+ background: [0, 0, 0],
|
|
|
+});
|
|
|
+
|
|
|
+fullScreenRenderer.addController(controlPanel);
|
|
|
+
|
|
|
+const actor = vtkVolume.newInstance();
|
|
|
+const mapper = vtkVolumeMapper.newInstance({
|
|
|
+ sampleDistance: 1.1,
|
|
|
+});
|
|
|
+
|
|
|
+const renderer = fullScreenRenderer.getRenderer();
|
|
|
+const renderWindow = fullScreenRenderer.getRenderWindow();
|
|
|
+
|
|
|
+mapper.setBlendModeToMaximumIntensity();
|
|
|
+
|
|
|
+renderer.getActiveCamera().setViewUp(0, 1, 0);
|
|
|
+
|
|
|
+
|
|
|
+//function sleep(ms) {
|
|
|
+// return new Promise(resolve => setTimeout(resolve, ms));
|
|
|
+//}
|
|
|
+
|
|
|
+
|
|
|
+function Redirect(output) {
|
|
|
+
|
|
|
+
|
|
|
+ // var win = window.open('/');
|
|
|
+ // await sleep(1000);
|
|
|
+
|
|
|
+// window.location.replace('/'+output+'')
|
|
|
+
|
|
|
+ let stateObj = { id: "100" };
|
|
|
+ window.history.replaceState(stateObj,
|
|
|
+ "index", '/'+output+'');
|
|
|
+
|
|
|
+ renderer.resetCamera();
|
|
|
+ const exampleContainer = document.querySelector('.content');
|
|
|
+ const rootBody = document.querySelector('body');
|
|
|
+ const myContainer = exampleContainer || rootBody;
|
|
|
+ load(myContainer, userParams, 'http://0.0.0.0:49000/'+output+'.vti');
|
|
|
+ renderWindow.render();
|
|
|
+
|
|
|
+ // win.document.head.innerHTML = '<title>Hi</title></head>';
|
|
|
+ // // win.document.body.innerHTML = '<body></body>';
|
|
|
+ // // win.document.write('
|
|
|
+ // var script = document.createElement('script');
|
|
|
+ // script.type = "module";
|
|
|
+ // // script.
|
|
|
+ // script.src = "http://localhost:1234/code.mjs";
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ // script.textContent = vtkFullScreenRenderWindow.newInstance({background: [0, 0, 0],});
|
|
|
+ // win.document.head.appendChild(script);
|
|
|
+ //
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ // renderer.resetCamera();
|
|
|
+ //
|
|
|
+ // const exampleContainer = document.querySelector('.content');
|
|
|
+ // const rootBody = document.querySelector('body');
|
|
|
+ // const myContainer = exampleContainer || rootBody;
|
|
|
+ // load(myContainer, userParams, 'http://localhost:1234/'+output+'.vti');
|
|
|
+ // renderWindow.render();
|
|
|
+
|
|
|
+ // load(myContainer, userParams, 'http://localhost:1234/'+output+'.vti');
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+function launch(output){
|
|
|
+ renderer.resetCamera();
|
|
|
+ const exampleContainer = win.document.querySelector('.content');
|
|
|
+ const rootBody = win.document.querySelector('body');
|
|
|
+ const myContainer = exampleContainer || rootBody;
|
|
|
+ load(myContainer, userParams, 'http://0.0.0.0:49000/'+output+'.vti');
|
|
|
+ renderWindow.render();
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+global.Redirect = Redirect;
|
|
|
+global.launch = launch;
|
|
|
+
|
|
|
+
|
|
|
+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(15);
|
|
|
+
|
|
|
+ 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();
|
|
|
+
|
|
|
+ // Pipeline handling
|
|
|
+ actor.setMapper(mapper);
|
|
|
+ mapper.setInputData(source);
|
|
|
+ renderer.addActor(actor);
|
|
|
+
|
|
|
+ // Configuration
|
|
|
+ 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().setInterpolationTypeToFastLinear();
|
|
|
+ actor.getProperty().setInterpolationTypeToLinear();
|
|
|
+
|
|
|
+ // For better looking volume rendering
|
|
|
+ // - distance in world coordinates a scalar opacity of 1.0
|
|
|
+ actor
|
|
|
+ .getProperty()
|
|
|
+ .setScalarOpacityUnitDistance(
|
|
|
+ 0,
|
|
|
+ vtkBoundingBox.getDiagonalLength(source.getBounds()) /
|
|
|
+ Math.max(...source.getDimensions())
|
|
|
+ );
|
|
|
+ // - control how we emphasize surface boundaries
|
|
|
+ // => max should be around the average gradient magnitude for the
|
|
|
+ // volume or maybe average plus one std dev of the gradient magnitude
|
|
|
+ // (adjusted for spacing, this is a world coordinate gradient, not a
|
|
|
+ // pixel gradient)
|
|
|
+ // => max hack: (dataRange[1] - dataRange[0]) * 0.05
|
|
|
+ actor.getProperty().setGradientOpacityMinimumValue(0, 0);
|
|
|
+ actor
|
|
|
+ .getProperty()
|
|
|
+ .setGradientOpacityMaximumValue(0, (dataRange[1] - dataRange[0]) * 0.05);
|
|
|
+ // - Use shading based on gradient
|
|
|
+ actor.getProperty().setShade(true);
|
|
|
+ actor.getProperty().setUseGradientOpacity(0, true);
|
|
|
+ // - generic good default
|
|
|
+ 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);
|
|
|
+
|
|
|
+
|
|
|
+ // Control UI
|
|
|
+ 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);
|
|
|
+
|
|
|
+ // setUpContent above sets the size to the container.
|
|
|
+ // We need to set the size after that.
|
|
|
+ // controllerWidget.setExpanded(false);
|
|
|
+
|
|
|
+ fullScreenRenderer.setResizeCallback(({ width, height }) => {
|
|
|
+ // 2px padding + 2x1px boder + 5px edge = 14
|
|
|
+ if (width > 414) {
|
|
|
+ controllerWidget.setSize(400, 150);
|
|
|
+ } else {
|
|
|
+ controllerWidget.setSize(width - 14, 150);
|
|
|
+ }
|
|
|
+ controllerWidget.render();
|
|
|
+ fpsMonitor.update();
|
|
|
+ });
|
|
|
+
|
|
|
+
|
|
|
+ // First render
|
|
|
+ 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, link) {
|
|
|
+ autoInit = false;
|
|
|
+ emptyContainer(container);
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ 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(link, {
|
|
|
+ progressCallback,
|
|
|
+ }).then((binary) => {
|
|
|
+ container.removeChild(progressContainer);
|
|
|
+ createViewer(container, binary, options);
|
|
|
+ });
|
|
|
+
|
|
|
+}
|
|
|
+
|