Volume

Introduction

vtk-js includes support for volume rendering using hardware GPU acceleration. The
capabilities may change over time but as of this writing it includes support
for :

  • multi-component data
  • at least 24 bits linear resolution
  • support for all native data types, 16 bit, float, etc
  • opacity transfer functions
  • color transfer functions
  • volume interpolation of nearest, fast linear and linear
  • automatic intermixing of opaque surfaces with volumes

Using volume rendering in vtk-js is very much like using it VTK is you are
familiar with VTK. The main objects are :

  • RenderWindow/Renderer as usual
  • Volume - similar to Actor, holds the property and mapper
  • VolumeMapper - takes an ImageData as input
  • VolumeProperty - holds the opacity and color transfer functions
  • PiecewiseFunction - a piecewise interpolated function, good for opacity
    transfer functions
  • ColorTransferFunction - similar to PiecewiseFunction but support an RGB
    value at each point.

Usage

// Load the rendering pieces we want to use (for both WebGL and WebGPU)
import 'vtk.js/Sources/Rendering/Profiles/Volume';

import vtkColorTransferFunction from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction';
import vtkHttpDataSetReader from '@kitware/vtk.js/IO/Core/HttpDataSetReader';

import vtkVolume from '@kitware/vtk.js/Rendering/Core/Volume';
import vtkVolumeMapper from '@kitware/vtk.js/Rendering/Core/VolumeMapper';

const vol = vtkVolume.newInstance();
const mapper = vtkVolumeMapper.newInstance();
mapper.setSampleDistance(2.0);
vol.setMapper(mapper);

const reader = vtkHttpDataSetReader.newInstance({ fetchGzip: true });
reader.setUrl(`${__BASE_PATH__}/Data/volume/headsq.vti`).then(() => {
reader.loadData().then(() => {
// we wait until the data is loaded before adding
// the volume to the renderer
renderer.addVolume(actor);
renderer.resetCamera();
renderWindow.render();
});
});

// create color and opacity transfer functions
const ctfun = vtkColorTransferFunction.newInstance();
ctfun.addRGBPoint(200.0, 1.0, 1.0, 1.0);
ctfun.addRGBPoint(2000.0, 0.0, 0.0, 0.0);

const ofun = vtkPiecewiseFunction.newInstance();
ofun.addPoint(200.0, 0.0);
ofun.addPoint(1200.0, 0.2);
ofun.addPoint(4000.0, 0.4);

vol.getProperty().setRGBTransferFunction(0, ctfun);
vol.getProperty().setScalarOpacity(0, ofun);
vol.getProperty().setScalarOpacityUnitDistance(0, 4.5);
vol.getProperty().setInterpolationTypeToFastLinear();

mapper.setInputConnection(reader.getOutputPort());

See Also

vtkColorTransferFunction

vtkImageData

vtkPiecewiseFunction

vtkVolumeMapper

vtkVolumeProperty

Methods

extend

Method use to decorate a given object (publicAPI+model) with vtkVolume characteristics.

Argument Type Required Description
publicAPI Yes object on which methods will be bounds (public)
model Yes object on which data structure will be bounds (protected)
initialValues IVolumeInitialValues No (default: {})

getBounds

Get the bounds for this mapper as [xmin, xmax, ymin, ymax,zmin, zmax].

getBoundsByReference

Get the bounds as [xmin, xmax, ymin, ymax, zmin, zmax].

getMTime

Get the Modified Time which is a monotonic increasing integer
global for all vtkObjects.

This allow to solve a question such as:

  • Is that object created/modified after another one?
  • Do I need to re-execute this filter, or not? …

getMapper

Get the volume mapper

getProperty

Get the volume property

getRedrawMTime

Get the mtime of anything that would cause the rendered image to appear
differently

getVolumes

For some exporters and other other operations we must be able to collect
all the actors or volumes.

makeProperty

Create a new property suitable for use with this type of Actor.

Argument Type Required Description
initialValues IVolumePropertyInitialValues No (default: {})

newInstance

Method use to create a new instance of vtkVolume

setMapper

Set the volume mapper

Argument Type Required Description
mapper vtkVolumeMapper Yes

setProperty

Set the volume property

Argument Type Required Description
property vtkVolumeProperty Yes

Source

index.d.ts
import { Bounds, Nullable } from "../../../types";
import vtkProp3D, { IProp3DInitialValues } from "../Prop3D";
import vtkVolumeMapper from "../VolumeMapper";
import vtkVolumeProperty, { IVolumePropertyInitialValues } from "../VolumeProperty";

/**
*
*/
export interface IVolumeInitialValues extends IProp3DInitialValues {
mapper?: vtkVolumeMapper;
property?: vtkVolumeProperty;
bounds?: Bounds;
}

/**
*
*/
export interface vtkVolume extends vtkProp3D {

/**
* Get the volume mapper
*/
getMapper(): Nullable<vtkVolumeMapper>;

/**
* For some exporters and other other operations we must be able to collect
* all the actors or volumes.
*/
getVolumes(): vtkVolume[];

/**
* Get the volume property
*/
getProperty(): vtkVolumeProperty;

/**
* Get the bounds for this mapper as [xmin, xmax, ymin, ymax,zmin, zmax].
* @return {Bounds} The bounds for the mapper.
*/
getBounds(): Bounds;

/**
* Get the bounds as [xmin, xmax, ymin, ymax, zmin, zmax].
*/
getBoundsByReference(): Bounds;

/**
* Get the `Modified Time` which is a monotonic increasing integer
* global for all vtkObjects.
*
* This allow to solve a question such as:
* - Is that object created/modified after another one?
* - Do I need to re-execute this filter, or not? ...
*
* @return {Number} the global modified time.
*/
getMTime(): number;

/**
* Get the mtime of anything that would cause the rendered image to appear
* differently
*/
getRedrawMTime(): number;

/**
* Create a new property suitable for use with this type of Actor.
* @param {IVolumePropertyInitialValues} [initialValues] (default: {})
*/
makeProperty(initialValues?: IVolumePropertyInitialValues): vtkVolumeProperty;

/**
* Set the volume mapper
* @param {vtkVolumeMapper} mapper
*/
setMapper(mapper: vtkVolumeMapper): boolean;

/**
* Set the volume property
* @param {vtkVolumeProperty} property
*/
setProperty(property: vtkVolumeProperty): boolean;
}

/**
* Method use to decorate a given object (publicAPI+model) with vtkVolume characteristics.
*
* @param publicAPI object on which methods will be bounds (public)
* @param model object on which data structure will be bounds (protected)
* @param {IVolumeInitialValues} [initialValues] (default: {})
*/
export function extend(publicAPI: object, model: object, initialValues?: IVolumeInitialValues): void;

/**
* Method use to create a new instance of vtkVolume
*/
export function newInstance(initialValues?: IVolumeInitialValues): vtkVolume;

/**
* vtk-js includes support for volume rendering using hardware GPU acceleration. The
* capabilities may change over time but as of this writing it includes support
* for :
*
* - multi-component data
* - at least 24 bits linear resolution
* - support for all native data types, 16 bit, float, etc
* - opacity transfer functions
* - color transfer functions
* - volume interpolation of nearest, fast linear and linear
* - automatic intermixing of opaque surfaces with volumes
*
* Using volume rendering in vtk-js is very much like using it VTK is you are
* familiar with VTK. The main objects are :
*
* - RenderWindow/Renderer as usual
* - Volume - similar to Actor, holds the property and mapper
* - VolumeMapper - takes an ImageData as input
* - VolumeProperty - holds the opacity and color transfer functions
* - PiecewiseFunction - a piecewise interpolated function, good for opacity
* transfer functions
* - ColorTransferFunction - similar to PiecewiseFunction but support an RGB
* value at each point.
*
* @example
* ```js
* // Load the rendering pieces we want to use (for both WebGL and WebGPU)
* import 'vtk.js/Sources/Rendering/Profiles/Volume';
*
* import vtkColorTransferFunction from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction';
* import vtkHttpDataSetReader from '@kitware/vtk.js/IO/Core/HttpDataSetReader';
*
* import vtkVolume from '@kitware/vtk.js/Rendering/Core/Volume';
* import vtkVolumeMapper from '@kitware/vtk.js/Rendering/Core/VolumeMapper';

* const vol = vtkVolume.newInstance();
* const mapper = vtkVolumeMapper.newInstance();
* mapper.setSampleDistance(2.0);
* vol.setMapper(mapper);
*
* const reader = vtkHttpDataSetReader.newInstance({ fetchGzip: true });
* reader.setUrl(`${__BASE_PATH__}/Data/volume/headsq.vti`).then(() => {
* reader.loadData().then(() => {
* // we wait until the data is loaded before adding
* // the volume to the renderer
* renderer.addVolume(actor);
* renderer.resetCamera();
* renderWindow.render();
* });
* });
*
* // create color and opacity transfer functions
* const ctfun = vtkColorTransferFunction.newInstance();
* ctfun.addRGBPoint(200.0, 1.0, 1.0, 1.0);
* ctfun.addRGBPoint(2000.0, 0.0, 0.0, 0.0);
*
* const ofun = vtkPiecewiseFunction.newInstance();
* ofun.addPoint(200.0, 0.0);
* ofun.addPoint(1200.0, 0.2);
* ofun.addPoint(4000.0, 0.4);
*
* vol.getProperty().setRGBTransferFunction(0, ctfun);
* vol.getProperty().setScalarOpacity(0, ofun);
* vol.getProperty().setScalarOpacityUnitDistance(0, 4.5);
* vol.getProperty().setInterpolationTypeToFastLinear();
*
* mapper.setInputConnection(reader.getOutputPort());
* ```
*
* @see [vtkColorTransferFunction](./Rendering_Core_ColorTransferFunction.html)
* @see [vtkImageData](./Common_DataModel_ImageData.html)
* @see [vtkPiecewiseFunction](./Common_DataModel_PiecewiseFunction.html)
* @see [vtkVolumeMapper](./Rendering_Core_VolumeMapper.html)
* @see [vtkVolumeProperty](./Rendering_Core_VolumeProperty.html)
*/
export declare const vtkVolume: {
newInstance: typeof newInstance,
extend: typeof extend,
};
export default vtkVolume;
index.js
import { vec3, mat4 } from 'gl-matrix';
import macro from 'vtk.js/Sources/macros';
import vtkBoundingBox from 'vtk.js/Sources/Common/DataModel/BoundingBox';
import vtkProp3D from 'vtk.js/Sources/Rendering/Core/Prop3D';
import vtkVolumeProperty from 'vtk.js/Sources/Rendering/Core/VolumeProperty';

const { vtkDebugMacro } = macro;

// ----------------------------------------------------------------------------
// vtkVolume methods
// ----------------------------------------------------------------------------

function vtkVolume(publicAPI, model) {
// Set our className
model.classHierarchy.push('vtkVolume');

publicAPI.getVolumes = () => publicAPI;

publicAPI.makeProperty = vtkVolumeProperty.newInstance;

publicAPI.getProperty = () => {
if (model.property === null) {
model.property = publicAPI.makeProperty();
}
return model.property;
};

publicAPI.getBounds = () => {
if (model.mapper === null) {
return model.bounds;
}

// Check for the special case when the mapper's bounds are unknown
const bds = model.mapper.getBounds();
if (!bds || bds.length !== 6) {
return bds;
}

// Check for the special case when the actor is empty.
if (bds[0] > bds[1]) {
model.mapperBounds = bds.concat(); // copy the mapper's bounds
model.bounds = [1, -1, 1, -1, 1, -1];
model.boundsMTime.modified();
return bds;
}

// Check if we have cached values for these bounds - we cache the
// values returned by model.mapper.getBounds() and we store the time
// of caching. If the values returned this time are different, or
// the modified time of this class is newer than the cached time,
// then we need to rebuild.
const zip = (rows) => rows[0].map((_, c) => rows.map((row) => row[c]));
if (
!model.mapperBounds ||
!zip([bds, model.mapperBounds]).reduce(
(a, b) => a && b[0] === b[1],
true
) ||
publicAPI.getMTime() > model.boundsMTime.getMTime()
) {
vtkDebugMacro('Recomputing bounds...');
model.mapperBounds = bds.map((x) => x);
const bbox = [];
vtkBoundingBox.getCorners(bds, bbox);
publicAPI.computeMatrix();
const tmp4 = new Float64Array(16);
mat4.transpose(tmp4, model.matrix);
bbox.forEach((pt) => vec3.transformMat4(pt, pt, tmp4));

/* eslint-disable no-multi-assign */
model.bounds[0] = model.bounds[2] = model.bounds[4] = Number.MAX_VALUE;
model.bounds[1] = model.bounds[3] = model.bounds[5] = -Number.MAX_VALUE;
/* eslint-enable no-multi-assign */
model.bounds = model.bounds.map((d, i) =>
i % 2 === 0
? bbox.reduce((a, b) => (a > b[i / 2] ? b[i / 2] : a), d)
: bbox.reduce((a, b) => (a < b[(i - 1) / 2] ? b[(i - 1) / 2] : a), d)
);
model.boundsMTime.modified();
}
return model.bounds;
};

publicAPI.getMTime = () => {
let mt = model.mtime;
if (model.property !== null) {
const time = model.property.getMTime();
mt = time > mt ? time : mt;
}
return mt;
};

publicAPI.getRedrawMTime = () => {
let mt = model.mtime;
if (model.mapper !== null) {
let time = model.mapper.getMTime();
mt = time > mt ? time : mt;
if (model.mapper.getInput() !== null) {
// FIXME !!! getInputAlgorithm / getInput
model.mapper.getInputAlgorithm().update();
time = model.mapper.getInput().getMTime();
mt = time > mt ? time : mt;
}
}
return mt;
};
}

// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------

const DEFAULT_VALUES = {
mapper: null,
property: null,
bounds: [1, -1, 1, -1, 1, -1],
};

// ----------------------------------------------------------------------------

export function extend(publicAPI, model, initialValues = {}) {
Object.assign(model, DEFAULT_VALUES, initialValues);

// Inheritance
vtkProp3D.extend(publicAPI, model, initialValues);

// vtkTimeStamp
model.boundsMTime = {};
macro.obj(model.boundsMTime);

// Build VTK API
macro.set(publicAPI, model, ['property']);
macro.setGet(publicAPI, model, ['mapper']);
macro.getArray(publicAPI, model, ['bounds'], 6);

// Object methods
vtkVolume(publicAPI, model);
}

// ----------------------------------------------------------------------------

export const newInstance = macro.newInstance(extend, 'vtkVolume');

// ----------------------------------------------------------------------------

export default { newInstance, extend };