@kitware/vtk.js
Version:
Visualization Toolkit for the Web
412 lines (350 loc) • 14.4 kB
JavaScript
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 };