UNPKG

@kitware/vtk.js

Version:

Visualization Toolkit for the Web

412 lines (350 loc) 14.4 kB
import _slicedToArray from '@babel/runtime/helpers/slicedToArray'; import _toConsumableArray from '@babel/runtime/helpers/toConsumableArray'; import macro from '../../macros.js'; import vtkBoundingBox from '../../Common/DataModel/BoundingBox.js'; import vtkImageCropFilter from '../../Filters/General/ImageCropFilter.js'; import vtkImageMapper from '../../Rendering/Core/ImageMapper.js'; import vtkImageSlice from '../../Rendering/Core/ImageSlice.js'; import vtkVolume from '../../Rendering/Core/Volume.js'; import vtkVolumeMapper from '../../Rendering/Core/VolumeMapper.js'; import vtkAbstractRepresentationProxy from '../Core/AbstractRepresentationProxy.js'; function sum(a, b) { return a + b; } // ---------------------------------------------------------------------------- function mean() { for (var _len = arguments.length, array = new Array(_len), _key = 0; _key < _len; _key++) { array[_key] = arguments[_key]; } return array.reduce(sum, 0) / array.length; } // ---------------------------------------------------------------------------- function updateDomains(dataset, dataArray, model, updateProp) { var dataRange = dataArray.getRange(); var spacing = dataset.getSpacing(); var bounds = dataset.getBounds(); var _model$mapperX$getClo = model.mapperX.getClosestIJKAxis(), xIJKAxis = _model$mapperX$getClo.ijkMode; var _model$mapperY$getClo = model.mapperY.getClosestIJKAxis(), yIJKAxis = _model$mapperY$getClo.ijkMode; var _model$mapperZ$getClo = model.mapperZ.getClosestIJKAxis(), zIJKAxis = _model$mapperZ$getClo.ijkMode; var propToUpdate = { xSlice: { domain: { min: bounds[0], max: bounds[1], step: spacing[xIJKAxis] } }, ySlice: { domain: { min: bounds[2], max: bounds[3], step: spacing[yIJKAxis] } }, zSlice: { domain: { min: bounds[4], max: bounds[5], step: spacing[zIJKAxis] } }, windowWidth: { domain: { min: 0, max: dataRange[1] - dataRange[0], step: 'any' } }, windowLevel: { domain: { min: dataRange[0], max: dataRange[1], step: 'any' } } }; updateProp('xSlice', propToUpdate.xSlice); updateProp('ySlice', propToUpdate.ySlice); updateProp('zSlice', propToUpdate.zSlice); updateProp('windowWidth', propToUpdate.windowWidth); updateProp('windowLevel', propToUpdate.windowLevel); return { xSlice: mean(propToUpdate.xSlice.domain.min, propToUpdate.xSlice.domain.max), ySlice: mean(propToUpdate.ySlice.domain.min, propToUpdate.ySlice.domain.max), zSlice: mean(propToUpdate.zSlice.domain.min, propToUpdate.zSlice.domain.max), windowWidth: propToUpdate.windowWidth.domain.max, windowLevel: Math.floor(mean(propToUpdate.windowLevel.domain.min, propToUpdate.windowLevel.domain.max)) }; } // ---------------------------------------------------------------------------- function updateConfiguration(dataset, dataArray, _ref) { _ref.mapper; var property = _ref.property; // Configuration // actor.getProperty().setInterpolationTypeToFastLinear(); property.setInterpolationTypeToLinear(); var numberOfComponents = dataArray.getNumberOfComponents(); var scalarOpacityUnitDistance = vtkBoundingBox.getDiagonalLength(dataset.getBounds()) / Math.max.apply(Math, _toConsumableArray(dataset.getDimensions())); for (var component = 0; component < numberOfComponents; component++) { // For better looking volume rendering // - distance in world coordinates a scalar opacity of 1.0 property.setScalarOpacityUnitDistance(component, scalarOpacityUnitDistance); var dataRange = dataArray.getRange(component); // - 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 property.setGradientOpacityMinimumValue(component, 0); property.setGradientOpacityMaximumValue(component, (dataRange[1] - dataRange[0]) * 0.05); // - Use shading based on gradient property.setShade(true); property.setUseGradientOpacity(component, true); // - generic good default property.setGradientOpacityMinimumOpacity(component, 0.0); property.setGradientOpacityMaximumOpacity(component, 1.0); } property.setAmbient(0.2); property.setDiffuse(0.7); property.setSpecular(0.3); property.setSpecularPower(8.0); } // ---------------------------------------------------------------------------- // vtkVolumeRepresentationProxy methods // ---------------------------------------------------------------------------- function vtkVolumeRepresentationProxy(publicAPI, model) { // Set our className model.classHierarchy.push('vtkVolumeRepresentationProxy'); // Volume model.mapper = vtkVolumeMapper.newInstance(); model.volume = vtkVolume.newInstance(); model.property = model.volume.getProperty(); model.cropFilter = vtkImageCropFilter.newInstance(); model.mapper.setInputConnection(model.cropFilter.getOutputPort()); model.sourceDependencies.push(model.cropFilter); // Slices model.mapperX = vtkImageMapper.newInstance({ slicingMode: vtkImageMapper.SlicingMode.X }); model.actorX = vtkImageSlice.newInstance({ visibility: false }); model.propertySlices = model.actorX.getProperty(); model.mapperY = vtkImageMapper.newInstance({ slicingMode: vtkImageMapper.SlicingMode.Y }); model.actorY = vtkImageSlice.newInstance({ visibility: false, property: model.propertySlices }); model.mapperZ = vtkImageMapper.newInstance({ slicingMode: vtkImageMapper.SlicingMode.Z }); model.actorZ = vtkImageSlice.newInstance({ visibility: false, property: model.propertySlices }); model.mapperX.setInputConnection(model.cropFilter.getOutputPort()); model.mapperY.setInputConnection(model.cropFilter.getOutputPort()); model.mapperZ.setInputConnection(model.cropFilter.getOutputPort()); // model.sourceDependencies.push(model.mapperX); // model.sourceDependencies.push(model.mapperY); // model.sourceDependencies.push(model.mapperZ); // connect rendering pipeline model.volume.setMapper(model.mapper); model.volumes.push(model.volume); // Connect slice pipeline model.actorX.setMapper(model.mapperX); model.actors.push(model.actorX); model.actorY.setMapper(model.mapperY); model.actors.push(model.actorY); model.actorZ.setMapper(model.mapperZ); model.actors.push(model.actorZ); function setInputData(inputDataset) { var _publicAPI$getColorBy = publicAPI.getColorBy(), _publicAPI$getColorBy2 = _slicedToArray(_publicAPI$getColorBy, 2), name = _publicAPI$getColorBy2[0], location = _publicAPI$getColorBy2[1]; publicAPI.rescaleTransferFunctionToDataRange(name, location); var lutProxy = publicAPI.getLookupTableProxy(name); var pwfProxy = publicAPI.getPiecewiseFunctionProxy(name); model.property.setRGBTransferFunction(0, lutProxy.getLookupTable()); model.property.setScalarOpacity(0, pwfProxy.getPiecewiseFunction()); updateConfiguration(inputDataset, publicAPI.getDataArray(), model); if (model.sampleDistance < 0 || model.sampleDistance > 1) { publicAPI.setSampleDistance(); } if (model.edgeGradient < 0 || model.edgeGradient > 1) { publicAPI.setEdgeGradient(); } // Update domains var state = updateDomains(inputDataset, publicAPI.getDataArray(), model, publicAPI.updateProxyProperty); publicAPI.set(state); // Check for 2D volumes var numberOfDimensions = inputDataset.getDimensions().reduce(function (number, dimension) { return number + (dimension > 1 ? 1 : 0); }, 0); if (numberOfDimensions === 2) { publicAPI.setIs2DVolume(true); } } model.sourceDependencies.push({ setInputData: setInputData }); // API ---------------------------------------------------------------------- /** * Choose whether the input volume should be treated as a 2D volume * (no volume rendering). */ publicAPI.setIs2DVolume = function (is2D) { model.is2DVolume = is2D; if (is2D) { if (publicAPI.getVisibility()) { publicAPI.setSliceVisibility(true); } model.volume.setVisibility(false); } }; publicAPI.isVisible = function () { return model.volume.getVisibility(); }; publicAPI.setVisibility = function (isVisible) { if (isVisible) { if (model.is2DVolume) { publicAPI.setSliceVisibility(true); } else { model.volume.setVisibility(true); } } else { // Turn off everything model.volume.setVisibility(false); publicAPI.setSliceVisibility(false); } }; publicAPI.getVisibility = function () { return model.volume.getVisibility() || publicAPI.getSliceVisibility(); }; publicAPI.isVisible = publicAPI.getVisibility; publicAPI.setSliceVisibility = function (isVisible) { if (isVisible && model.is2DVolume) { var normalAxis = publicAPI.getInputDataSet().getDimensions().indexOf(1); if (model.actors[normalAxis]) { model.actors[normalAxis].setVisibility(true); } } else { model.actors.forEach(function (actor) { return actor.setVisibility(isVisible); }); } }; publicAPI.getSliceVisibility = function () { return model.actorX.getVisibility() || model.actorY.getVisibility() || model.actorZ.getVisibility(); }; publicAPI.setSampleDistance = function () { var distance = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0.4; if (model.sampleDistance !== distance) { model.sampleDistance = distance; var sourceDS = publicAPI.getInputDataSet(); var sampleDistance = 0.7 * Math.sqrt(sourceDS.getSpacing().map(function (v) { return v * v; }).reduce(function (a, b) { return a + b; }, 0)); model.mapper.setSampleDistance(sampleDistance * Math.pow(2, distance * 3.0 - 1.5)); publicAPI.modified(); } }; publicAPI.setEdgeGradient = function () { var edgeGradient = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0.2; if (model.edgeGradient !== edgeGradient) { model.edgeGradient = edgeGradient; var dataArray = publicAPI.getDataArray(); var numberOfComponents = dataArray.getNumberOfComponents(); if (edgeGradient === 0) { for (var component = 0; component < numberOfComponents; component++) { model.volume.getProperty().setUseGradientOpacity(component, false); } } else { for (var _component = 0; _component < numberOfComponents; _component++) { var dataRange = dataArray.getRange(_component); model.volume.getProperty().setUseGradientOpacity(_component, true); var minV = Math.max(0.0, edgeGradient - 0.3) / 0.7; if (minV > 0.0) { model.volume.getProperty().setGradientOpacityMinimumValue(_component, Math.exp(Math.log((dataRange[1] - dataRange[0]) * 0.2) * minV * minV)); } else { model.volume.getProperty().setGradientOpacityMinimumValue(_component, 0.0); } model.volume.getProperty().setGradientOpacityMaximumValue(_component, Math.exp(Math.log((dataRange[1] - dataRange[0]) * 1.0) * edgeGradient * edgeGradient)); } } publicAPI.modified(); } }; var parentSetColorBy = publicAPI.setColorBy; publicAPI.setColorBy = function (arrayName, arrayLocation) { var componentIndex = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : -1; parentSetColorBy(arrayName, arrayLocation, componentIndex); var lutProxy = publicAPI.getLookupTableProxy(arrayName); var pwfProxy = publicAPI.getPiecewiseFunctionProxy(arrayName); model.property.setRGBTransferFunction(0, lutProxy.getLookupTable()); model.property.setScalarOpacity(0, pwfProxy.getPiecewiseFunction()); }; } // ---------------------------------------------------------------------------- // Object factory // ---------------------------------------------------------------------------- var DEFAULT_VALUES = { sampleDistance: -1, edgeGradient: -1, disableSolidColor: true, is2DVolume: false }; // ---------------------------------------------------------------------------- function extend(publicAPI, model) { var initialValues = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; Object.assign(model, DEFAULT_VALUES, initialValues); // Object methods vtkAbstractRepresentationProxy.extend(publicAPI, model, initialValues); macro.get(publicAPI, model, ['sampleDistance', 'edgeGradient', 'cropFilter', 'is2DVolume']); // Object specific methods vtkVolumeRepresentationProxy(publicAPI, model); macro.proxyPropertyMapping(publicAPI, model, { xSlice: { modelKey: 'mapperX', property: 'slice' }, ySlice: { modelKey: 'mapperY', property: 'slice' }, zSlice: { modelKey: 'mapperZ', property: 'slice' }, volumeVisibility: { modelKey: 'volume', property: 'visibility' }, xSliceVisibility: { modelKey: 'actorX', property: 'visibility' }, ySliceVisibility: { modelKey: 'actorY', property: 'visibility' }, zSliceVisibility: { modelKey: 'actorZ', property: 'visibility' }, windowWidth: { modelKey: 'propertySlices', property: 'colorWindow' }, windowLevel: { modelKey: 'propertySlices', property: 'colorLevel' }, useShadow: { modelKey: 'property', property: 'shade' }, croppingPlanes: { modelKey: 'cropFilter', property: 'croppingPlanes' } }); } // ---------------------------------------------------------------------------- var newInstance = macro.newInstance(extend, 'vtkVolumeRepresentationProxy'); // ---------------------------------------------------------------------------- var vtkVolumeRepresentationProxy$1 = { newInstance: newInstance, extend: extend, updateConfiguration: updateConfiguration }; export { vtkVolumeRepresentationProxy$1 as default, extend, newInstance };