Commit f222dd65 authored by Christian Schulte zu Berge's avatar Christian Schulte zu Berge
Browse files

Merge branch 'tensorglyphrenderer' of /mnt/bigone/git/repositories/berge/campvis into development

parents 1b676808 2d96cece
......@@ -54,7 +54,7 @@ uniform mat4 _projectionMatrix = mat4(
void main() {
gl_Position = _projectionMatrix * (_viewMatrix * (_modelMatrix * vec4(in_Position, 1.0)));
ex_Position = gl_Position;
ex_Position = vec4(in_Position, 1.0);
ex_TexCoord = in_TexCoord;
ex_Color = in_Color;
......
// ================================================================================================
//
// This file is part of the CAMPVis Software Framework.
//
// If not explicitly stated otherwise: Copyright (C) 2012-2013, all rights reserved,
// Christian Schulte zu Berge <christian.szb@in.tum.de>
// Chair for Computer Aided Medical Procedures
// Technische Universität München
// Boltzmannstr. 3, 85748 Garching b. München, Germany
//
// For a full list of authors and contributors, please refer to the file "AUTHORS.txt".
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
// except in compliance with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software distributed under the
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
// either express or implied. See the License for the specific language governing permissions
// and limitations under the License.
//
// ================================================================================================
#include "tools/shading.frag"
in vec3 ex_Position; ///< incoming texture coordinate
in vec3 ex_Normal; ///< incoming texture coordinate
out vec4 out_Color; ///< outgoing fragment color
uniform vec4 _color;
uniform LightSource _lightSource;
uniform vec3 _cameraPosition;
void main() {
out_Color = _color;
#ifdef ENABLE_SHADING
// compute gradient (needed for shading and normals)
vec3 gradient = ex_Normal;
out_Color.rgb = calculatePhongShading(ex_Position, _lightSource, _cameraPosition, gradient, _color.rgb, _color.rgb, vec3(1.0, 1.0, 1.0));
#endif
//out_Color = vec4(ex_Normal, 1.0);
}
// ================================================================================================
//
// This file is part of the CAMPVis Software Framework.
//
// If not explicitly stated otherwise: Copyright (C) 2012-2013, all rights reserved,
// Christian Schulte zu Berge <christian.szb@in.tum.de>
// Chair for Computer Aided Medical Procedures
// Technische Universität München
// Boltzmannstr. 3, 85748 Garching b. München, Germany
//
// For a full list of authors and contributors, please refer to the file "AUTHORS.txt".
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
// except in compliance with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software distributed under the
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
// either express or implied. See the License for the specific language governing permissions
// and limitations under the License.
//
// ================================================================================================
layout(location = 0) in vec3 in_Position; ///< incoming vertex position
layout(location = 3) in vec3 in_Normal; ///< incoming normal direction
out vec3 ex_Position; ///< outgoing world coordinates
out vec3 ex_Normal; ///< outgoing normal direction
/// Matrix defining model-to-world transformation
uniform mat4 _modelMatrix = mat4(
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0);
/// Matrix defining view transformation
uniform mat4 _viewMatrix = mat4(
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0);
/// Matrix defining projection transformation
uniform mat4 _projectionMatrix = mat4(
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0);
void main() {
gl_Position = _projectionMatrix * (_viewMatrix * (_modelMatrix * vec4(in_Position, 1.0)));
ex_Position = in_Position;
mat4 normalMatrix = transpose(inverse(_modelMatrix));
vec4 normalTmp = (normalMatrix * vec4(in_Normal, 0.0));
ex_Normal = normalize((normalTmp).xyz);
}
......@@ -35,13 +35,25 @@ namespace campvis {
: AutoEvaluationPipeline(dc)
, _imageReader()
, _ta()
, _sliceExtractor(&_canvasSize)
, _wheelHandler(&_sliceExtractor.p_zSliceNumber)
, _glyphRenderer(&_canvasSize)
, _sliceRenderer(&_canvasSize)
, _rtc(&_canvasSize)
, p_camera("Camera", "Camera", tgt::Camera())
, p_sliceNumber("SliceNuber", "Slice Number", 0, 0, 256)
, _trackballEH(0)
{
addProperty(&p_camera);
addProperty(&p_sliceNumber);
_trackballEH = new TrackballNavigationEventListener(&p_camera, &_canvasSize);
addEventListenerToBack(_trackballEH);
addProcessor(&_imageReader);
addProcessor(&_ta);
addProcessor(&_sliceExtractor);
addEventListenerToBack(&_wheelHandler);
addProcessor(&_glyphRenderer);
addProcessor(&_sliceRenderer);
addProcessor(&_rtc);
}
TensorDemo::~TensorDemo() {
......@@ -50,21 +62,46 @@ namespace campvis {
void TensorDemo::init() {
AutoEvaluationPipeline::init();
p_camera.addSharedProperty(&_glyphRenderer.p_camera);
p_camera.addSharedProperty(&_sliceRenderer.p_camera);
p_sliceNumber.addSharedProperty(&_glyphRenderer.p_sliceNumber);
p_sliceNumber.addSharedProperty(&_sliceRenderer.p_sliceNumber);
_imageReader.p_url.setValue(CAMPVIS_SOURCE_DIR "/modules/tensor/sampledata/planar_tensor.mhd");
_imageReader.p_targetImageID.setValue("reader.output");
_imageReader.p_targetImageID.addSharedProperty(&_ta.p_inputImage);
_ta.p_outputProperties[0]->_imageId.addSharedProperty(&_sliceExtractor.p_sourceImageID);
_ta.p_outputProperties[0]->_imageType.selectById("MainEigenvector");
_ta.p_outputProperties[0]->_imageId.addSharedProperty(&_sliceRenderer.p_sourceImageID);
_ta.p_outputProperties[0]->_imageType.selectById("Trace");
_ta.p_evalsImage.addSharedProperty(&_glyphRenderer.p_inputEigenvalues);
_ta.p_evecsImage.addSharedProperty(&_glyphRenderer.p_inputEigenvectors);
_ta.s_validated.connect(this, &TensorDemo::onProcessorValidated);
_sliceExtractor.p_xSliceNumber.setValue(0);
_glyphRenderer.p_renderOutput.setValue("glyphs");
_glyphRenderer.p_renderOutput.addSharedProperty(&_rtc.p_firstImageId);
Geometry1DTransferFunction* tf = new Geometry1DTransferFunction(128, tgt::vec2(0.f, 1.f));
tf->addGeometry(TFGeometry1D::createQuad(tgt::vec2(0.f, 1.f), tgt::col4(0, 0, 0, 0), tgt::col4(255, 255, 255, 255)));
_sliceExtractor.p_transferFunction.replaceTF(tf);
_sliceRenderer.p_transferFunction.replaceTF(tf);
_sliceRenderer.p_targetImageID.setValue("slice");
_sliceRenderer.p_targetImageID.addSharedProperty(&_rtc.p_secondImageId);
_rtc.p_compositingMethod.selectById("depth");
_rtc.p_targetImageId.setValue("composed");
_renderTargetID.setValue("renderTarget");
_renderTargetID.addSharedProperty(&(_sliceExtractor.p_targetImageID));
_renderTargetID.setValue("composed");
}
void TensorDemo::onProcessorValidated(AbstractProcessor* processor) {
if (processor == &_ta) {
// update camera
ScopedTypedData<IHasWorldBounds> img(*_data, _ta.p_evalsImage.getValue());
if (img) {
_trackballEH->reinitializeCamera(img);
}
}
}
}
......@@ -25,12 +25,16 @@
#ifndef TENSORDEMO_H__
#define TENSORDEMO_H__
#include "core/eventhandlers/mwheeltonumericpropertyeventlistener.h"
#include "core/pipeline/autoevaluationpipeline.h"
#include "core/eventhandlers/mwheeltonumericpropertyeventlistener.h"
#include "core/eventhandlers/trackballnavigationeventlistener.h"
#include "modules/io/processors/mhdimagereader.h"
#include "modules/tensor/processors/tensoranalyzer.h"
#include "modules/vis/processors/sliceextractor.h"
#include "modules/tensor/processors/tensorglyphrenderer.h"
#include "modules/vis/processors/slicerenderer3d.h"
#include "modules/vis/processors/rendertargetcompositor.h"
namespace campvis {
class TensorDemo : public AutoEvaluationPipeline {
......@@ -54,11 +58,22 @@ namespace campvis {
static const std::string getId() { return "TensorDemo"; };
protected:
/**
* Slot getting called when one of the observed processors got validated.
* Updates the camera properties, when the input image has changed.
* \param processor The processor that emitted the signal
*/
virtual void onProcessorValidated(AbstractProcessor* processor);
MhdImageReader _imageReader;
TensorAnalyzer _ta;
SliceExtractor _sliceExtractor;
TensorGlyphRenderer _glyphRenderer;
SliceRenderer3D _sliceRenderer;
RenderTargetCompositor _rtc;
MWheelToNumericPropertyEventListener _wheelHandler;
CameraProperty p_camera;
IntProperty p_sliceNumber;
TrackballNavigationEventListener* _trackballEH;
};
}
......
......@@ -81,9 +81,14 @@ namespace campvis {
/// \see AbstractProcessor::deinit()
virtual void deinit();
/**
* Adds another output for this processor (i.e. adds another OutputPropertyPair).
*/
void addOutput();
DataNameProperty p_inputImage; ///< ID for input volume
DataNameProperty p_evalsImage; ///< ID for output gradient volume
DataNameProperty p_evecsImage; ///< ID for output gradient volume
DataNameProperty p_evalsImage; ///< ID for output eigenvalue volume
DataNameProperty p_evecsImage; ///< ID for output eigenvector volume
GenericOptionProperty<DegeneratedEvHandling> p_degeneratedHandling; ///< Handling of degenerated tensors
BoolProperty p_maskMixedTensors;
......@@ -109,11 +114,6 @@ namespace campvis {
*/
void computeOutput(DataContainer& data, size_t index);
/**
* Adds another output for this processor (i.e. adds another OutputPropertyPair).
*/
void addOutput();
DataHandle _eigenvalues; ///< Current eigenvalues cached
DataHandle _eigenvectors; ///< Current eigenvectors cached
......
// ================================================================================================
//
// This file is part of the CAMPVis Software Framework.
//
// If not explicitly stated otherwise: Copyright (C) 2012-2013, all rights reserved,
// Christian Schulte zu Berge <christian.szb@in.tum.de>
// Chair for Computer Aided Medical Procedures
// Technische Universität München
// Boltzmannstr. 3, 85748 Garching b. München, Germany
//
// For a full list of authors and contributors, please refer to the file "AUTHORS.txt".
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
// except in compliance with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software distributed under the
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
// either express or implied. See the License for the specific language governing permissions
// and limitations under the License.
//
// ================================================================================================
#include "TensorGlyphRenderer.h"
#include "tgt/tgt_math.h"
#include "tgt/logmanager.h"
#include "tgt/shadermanager.h"
#include "core/datastructures/imagedata.h"
#include "core/datastructures/geometrydatafactory.h"
#include "core/datastructures/renderdata.h"
#include "core/pipeline/processordecoratorshading.h"
namespace campvis {
static const GenericOption<TensorGlyphRenderer::GlyphType> glyphTypes[3] = {
GenericOption<TensorGlyphRenderer::GlyphType>("ellipsoid", "Ellipsoid Glyph", TensorGlyphRenderer::ELLIPSOID),
GenericOption<TensorGlyphRenderer::GlyphType>("cuboid", "Cuboid Glyph", TensorGlyphRenderer::CUBOID),
GenericOption<TensorGlyphRenderer::GlyphType>("multi", "Multi Ellipsoid Glyph", TensorGlyphRenderer::MULTI)
};
static const GenericOption<TensorGlyphRenderer::SliceOrientation> sliceOrientationOptions[3] = {
GenericOption<TensorGlyphRenderer::SliceOrientation>("z", "XY Plane", TensorGlyphRenderer::XY_PLANE),
GenericOption<TensorGlyphRenderer::SliceOrientation>("y", "XZ Plane", TensorGlyphRenderer::XZ_PLANE),
GenericOption<TensorGlyphRenderer::SliceOrientation>("x", "YZ Plane", TensorGlyphRenderer::YZ_PLANE)
};
const std::string TensorGlyphRenderer::loggerCat_ = "CAMPVis.modules.classification.TensorGlyphRenderer";
TensorGlyphRenderer::TensorGlyphRenderer(IVec2Property* viewportSizeProp)
: VisualizationProcessor(viewportSizeProp)
, p_inputEigenvalues("InputEigenvalues", "Input Eigenvalues Image", "eigenvalues", DataNameProperty::READ, INVALID_RESULT | INVALID_PROPERTIES)
, p_inputEigenvectors("InputEigenvectors", "Input Eigenvectors Image", "eigenvectors", DataNameProperty::READ, INVALID_RESULT | INVALID_PROPERTIES)
, p_renderOutput("RenderOutput", "Output Image", "TensorGlyphRenderer.output", DataNameProperty::WRITE)
, p_glyphType("GlyphType", "Glyph Type to Render", glyphTypes, 3)
, p_glyphSize("GlyphSize", "Glyph Size", 1.f, .1f, 5.f)
, p_camera("Camera", "Camera", tgt::Camera())
, p_sliceOrientation("SliceOrientation", "Slice Orientation", sliceOrientationOptions, 3, INVALID_RESULT | INVALID_PROPERTIES)
, p_sliceNumber("SliceNumber", "Slice Number", 0, 0, 0)
, _ellipsoidGeometry(0)
, _cubeGeometry(0)
{
addDecorator(new ProcessorDecoratorShading());
addProperty(&p_inputEigenvalues);
addProperty(&p_inputEigenvectors);
addProperty(&p_renderOutput);
addProperty(&p_glyphType);
addProperty(&p_glyphSize);
addProperty(&p_camera);
addProperty(&p_sliceOrientation);
addProperty(&p_sliceNumber);
decoratePropertyCollection(this);
}
TensorGlyphRenderer::~TensorGlyphRenderer() {
}
void TensorGlyphRenderer::init() {
VisualizationProcessor::init();
_shader = ShdrMgr.load("modules/tensor/glsl/tensorglyphrenderer.vert", "modules/tensor/glsl/tensorglyphrenderer.frag", generateGlslHeader());
_cubeGeometry = GeometryDataFactory::createCube(tgt::Bounds(tgt::vec3(-.5f), tgt::vec3(.5f)), tgt::Bounds(tgt::vec3(0.f), tgt::vec3(1.f)));
_ellipsoidGeometry = GeometryDataFactory::createSphere(8, 16);
}
void TensorGlyphRenderer::deinit() {
ShdrMgr.dispose(_shader);
delete _ellipsoidGeometry;
_ellipsoidGeometry = 0;
delete _cubeGeometry;
_cubeGeometry = 0;
VisualizationProcessor::deinit();
}
void TensorGlyphRenderer::updateResult(DataContainer& dataContainer) {
if (_cubeGeometry == 0 || _ellipsoidGeometry == 0) {
LERROR("Error initializing glyph geometries.");
return;
}
GenericImageRepresentationLocal<float, 3>::ScopedRepresentation evals(dataContainer, p_inputEigenvalues.getValue());
GenericImageRepresentationLocal<float, 9>::ScopedRepresentation evecs(dataContainer, p_inputEigenvectors.getValue());
if (evals && evecs) {
if (evals->getSize() == evecs->getSize()) {
const tgt::Camera& cam = p_camera.getValue();
const tgt::svec3& imgSize = evals->getSize();
glEnable(GL_DEPTH_TEST);
_shader->activate();
_shader->setIgnoreUniformLocationError(true);
_shader->setUniform("_viewportSizeRCP", 1.f / tgt::vec2(getEffectiveViewportSize()));
_shader->setUniform("_projectionMatrix", cam.getProjectionMatrix());
_shader->setUniform("_viewMatrix", cam.getViewMatrix());
decorateRenderProlog(dataContainer, _shader);
FramebufferActivationGuard fag(this);
createAndAttachColorTexture();
createAndAttachDepthTexture();
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
switch (p_sliceOrientation.getOptionValue()) {
case XY_PLANE:
for (size_t x = 0; x < imgSize.x; ++x) {
for (size_t y = 0; y < imgSize.y; ++y) {
renderTensorGlyph(evals, evecs, tgt::ivec3(static_cast<int>(x), static_cast<int>(y), p_sliceNumber.getValue()));
}
}
break;
case XZ_PLANE:
for (size_t x = 0; x < imgSize.x; ++x) {
for (size_t z = 0; z < imgSize.z; ++z) {
renderTensorGlyph(evals, evecs, tgt::ivec3(static_cast<int>(x), p_sliceNumber.getValue(), static_cast<int>(z)));
}
}
break;
case YZ_PLANE:
for (size_t y = 0; y < imgSize.y; ++y) {
for (size_t z = 0; z < imgSize.z; ++z) {
renderTensorGlyph(evals, evecs, tgt::ivec3(p_sliceNumber.getValue(), static_cast<int>(y), static_cast<int>(z)));
}
}
break;
}
decorateRenderEpilog(_shader);
_shader->deactivate();
glDisable(GL_DEPTH_TEST);
dataContainer.addData(p_renderOutput.getValue(), new RenderData(_fbo));
}
}
else {
LERROR("Could not find suitable input data.");
}
validate(INVALID_RESULT);
}
void TensorGlyphRenderer::updateProperties(DataContainer& dataContainer) {
GenericImageRepresentationLocal<float, 3>::ScopedRepresentation evals(dataContainer, p_inputEigenvalues.getValue());
GenericImageRepresentationLocal<float, 9>::ScopedRepresentation evecs(dataContainer, p_inputEigenvectors.getValue());
if (evals && evecs) {
if (evals->getSize() == evecs->getSize()) {
switch (p_sliceOrientation.getOptionValue()) {
case XY_PLANE:
p_sliceNumber.setMaxValue(static_cast<int>(evals->getSize().z - 1));
break;
case XZ_PLANE:
p_sliceNumber.setMaxValue(static_cast<int>(evals->getSize().y - 1));
break;
case YZ_PLANE:
p_sliceNumber.setMaxValue(static_cast<int>(evals->getSize().x - 1));
break;
}
}
else {
LERROR("Size of eigenvalue image and eigenvector image mismatch!");
}
}
validate(INVALID_PROPERTIES);
}
void TensorGlyphRenderer::updateShader() {
_shader->setHeaders(generateGlslHeader());
_shader->rebuild();
validate(INVALID_SHADER);
}
std::string TensorGlyphRenderer::generateGlslHeader() const {
std::string toReturn = getDecoratedHeader();
return toReturn;
}
void TensorGlyphRenderer::renderTensorGlyph(const GenericImageRepresentationLocal<float, 3>* evals, const GenericImageRepresentationLocal<float, 9>* evecs, const tgt::vec3& position) {
/// minimum scale factor
const float EPS = .1f;
// gather value
const tgt::vec3& eigenvalues = evals->getElement(position);
const tgt::mat3& eigenvectors = evecs->getElement(position);
if (eigenvalues == tgt::vec3::zero || eigenvectors == tgt::mat3::zero)
return;
// compute rotation matrix
tgt::vec3 rotx = tgt::normalize(eigenvectors[0]);
tgt::vec3 roty = tgt::normalize(eigenvectors[1]);
tgt::vec3 rotz = tgt::normalize(eigenvectors[2]);
tgt::mat4 rotationMatrix(rotx[0], rotx[1], rotx[2], 0.f,
roty[0], roty[1], roty[2], 0.f,
rotz[0], rotz[1], rotz[2], 0.f,
0.f , 0.f , 0.f , 1.f);
float divScale = (1.f - 2.f*EPS)/(eigenvalues[0]);
const tgt::mat4& voxelToWorldMatrix = evals->getParent()->getMappingInformation().getVoxelToWorldMatrix();
// compute model matrix (without glyph-related transformation
tgt::mat4 modelMatrix = voxelToWorldMatrix * tgt::mat4::createTranslation(position) * rotationMatrix * tgt::mat4::createScale(tgt::vec3(p_glyphSize.getValue()));
// setup shader
_shader->setUniform("_color", tgt::vec4(rotx, 1.f));
switch (p_glyphType.getOptionValue()) {
case CUBOID:
// render single cuboid
_shader->setUniform("_modelMatrix", modelMatrix * tgt::mat4::createScale(tgt::vec3((1.f - EPS), (EPS + divScale*eigenvalues[1]), (EPS + divScale*eigenvalues[2]))));
_cubeGeometry->render(GL_POLYGON);
break;
case ELLIPSOID:
// render single ellipsoid
_shader->setUniform("_modelMatrix", modelMatrix * tgt::mat4::createScale(tgt::vec3((1.f - EPS), (EPS + divScale*eigenvalues[1]), (EPS + divScale*eigenvalues[2]))));
_ellipsoidGeometry->render(GL_TRIANGLE_STRIP);
break;
case MULTI:
// render three ellipsoids in different shapes
_shader->setUniform("_modelMatrix", modelMatrix * tgt::mat4::createScale(tgt::vec3(divScale*eigenvalues[2], divScale*eigenvalues[2], divScale*eigenvalues[2])));
_ellipsoidGeometry->render(GL_TRIANGLE_STRIP);
_shader->setUniform("_modelMatrix", modelMatrix * tgt::mat4::createScale(tgt::vec3(divScale*eigenvalues[1], divScale*eigenvalues[1], EPS)));
_ellipsoidGeometry->render(GL_TRIANGLE_STRIP);
_shader->setUniform("_modelMatrix", modelMatrix * tgt::mat4::createScale(tgt::vec3(divScale*eigenvalues[0], EPS, EPS)));
_ellipsoidGeometry->render(GL_TRIANGLE_STRIP);
break;
}
}
}
// ================================================================================================
//
// This file is part of the CAMPVis Software Framework.
//
// If not explicitly stated otherwise: Copyright (C) 2012-2013, all rights reserved,
// Christian Schulte zu Berge <christian.szb@in.tum.de>
// Chair for Computer Aided Medical Procedures
// Technische Universität München
// Boltzmannstr. 3, 85748 Garching b. München, Germany
//
// For a full list of authors and contributors, please refer to the file "AUTHORS.txt".
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
// except in compliance with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software distributed under the
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
// either express or implied. See the License for the specific language governing permissions
// and limitations under the License.
//
// ================================================================================================
#ifndef TENSORGLYPHRENDERER_H__
#define TENSORGLYPHRENDERER_H__
#include <string>
#include "core/pipeline/visualizationprocessor.h"
#include "core/pipeline/abstractprocessordecorator.h"
#include "core/properties/cameraproperty.h"
#include "core/properties/datanameproperty.h"
#include "core/properties/floatingpointproperty.h"
#include "core/properties/genericproperty.h"
#include "core/properties/numericproperty.h"
#include "core/properties/optionproperty.h"
#include "core/datastructures/genericimagerepresentationlocal.h"
#include "core/datastructures/geometrydata.h"
namespace tgt {
class Shader;
}
namespace campvis {
/**
* Renders axis-aligned slices with tensor glyphs.
*/
class TensorGlyphRenderer : public VisualizationProcessor, public HasProcessorDecorators {