MedicalDemo4
Repository source: MedicalDemo4
Description¶
Volume rendering of the dataset.
Usage
MedicalDemo4 FullHead.mhd
Note
The original source code for this example is here.
Info
The example uses src/Testing/Data/FullHead.mhd which references src/Testing/Data/FullHead.raw.gz.
Other languages
See (Python), (PythonicAPI), (Java)
Question
If you have a question about this example, please use the VTK Discourse Forum
Code¶
MedicalDemo4.cxx
// Derived from VTK/Examples/Cxx/Medical4.cxx
// This example reads a volume dataset and displays it via volume rendering.
//
#include <vtkCamera.h>
#include <vtkCameraOrientationRepresentation.h>
#include <vtkCameraOrientationWidget.h>
#include <vtkColorTransferFunction.h>
#include <vtkFixedPointVolumeRayCastMapper.h>
#include <vtkImageChangeInformation.h>
#include <vtkImageData.h>
#include <vtkInteractorStyleSwitch.h>
#include <vtkMapper.h>
#include <vtkMetaImageReader.h>
#include <vtkNamedColors.h>
#include <vtkNew.h>
#include <vtkOutlineFilter.h>
#include <vtkPiecewiseFunction.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkRenderer.h>
#include <vtkTransform.h>
#include <vtkTransformFilter.h>
#include <vtkVolume.h>
#include <vtkVolumeProperty.h>
#include <vtk_cli11.h>
#include <vtk_fmt.h>
// clang-format off
#include VTK_FMT(fmt/format.h)
// clang-format on
#include <array>
#include <filesystem>
namespace fs = std::filesystem;
namespace {
typedef std::map<std::string, std::string> TAxisParams;
typedef std::map<std::string, TAxisParams> TAxesParams;
/**
* @brief Define the axes labels.
*
* @return The axes labels.
*/
TAxesParams DefineAxesLabels();
/**
* @brief Define the axes colors.
*
* @return The axes colors.
*/
TAxesParams DefineAxesColors();
/**
* @brief Gather the defined axes labels and colors into a map.
*
* @return The map of axes labels and colors.
*/
std::map<std::string, std::pair<TAxisParams, TAxisParams>> GetAxesParams();
/**
* @brief Make a camera orientation widget for a given renderer.
*
* position has these values 0: LowerLeft, 1: UpperLeft, 2: LowerRight, 3:
* UpperRight
*
* @param ren The renderer.
* @param alc The key specifying the desired labels and colors for the axes.
* @param colors A reference to the vtkNamedColors object.
* @param position Move the camera orientation widget to upper left.
*
* @return The camera orientation widget.
*/
vtkNew<vtkCameraOrientationWidget>
MakeCameraOrientationWidget(vtkRenderer* ren, std::string alcKey = "xyz",
int const& position = 3);
} // namespace
int main(int argc, char* argv[])
{
CLI::App app{"Read a volume dataset and display it via volume rendering."};
// Define options
std::string fileName;
app.add_option("fileName", fileName,
"The path to the data file e.g. FullHead.mhd.")
->required()
->check(CLI::ExistingFile);
CLI11_PARSE(app, argc, argv);
vtkNew<vtkNamedColors> colors;
std::array<unsigned char, 4> bkg{{51, 77, 102, 255}};
colors->SetColor("BkgColor", bkg.data());
// Create the renderer, the render window, and the interactor. The renderer
// draws into the render window, the interactor enables mouse- and
// keyboard-based interaction with the scene.
vtkNew<vtkRenderer> ren;
ren->SetBackground(colors->GetColor3d("BkgColor").GetData());
vtkNew<vtkRenderWindow> renWin;
renWin->SetSize(640, 480);
auto appFn = fs::path((app.get_name())).stem().string();
renWin->SetWindowName(appFn.c_str());
renWin->AddRenderer(ren);
vtkNew<vtkRenderWindowInteractor> iren;
iren->SetRenderWindow(renWin);
auto is = vtkInteractorStyleSwitch::SafeDownCast(iren->GetInteractorStyle());
if (is)
{
is->SetCurrentStyleToTrackballCamera();
}
// The following reader is used to read a series of 2D slices (images)
// that compose the volume. The slice dimensions are set, and the
// pixel spacing. The data Endianness must also be specified. The reader
// uses the FilePrefix in combination with the slice number to construct
// filenames using the format FilePrefix.%d. (In this case the FilePrefix
// is the root name of the file: quarter.)
vtkNew<vtkMetaImageReader> reader;
reader->SetFileName(fileName.c_str());
reader->SetDataByteOrderToLittleEndian();
reader->Update();
// Get the current physical center of the volume
// Bounds returns: [xmin, xmax, ymin, ymax, zmin, zmax]
auto bounds = reader->GetOutput()->GetBounds();
std::vector<double> centroid;
for (auto i = 0; i < 6; i += 2)
{
centroid.push_back(bounds[i] + (bounds[i + 1] - bounds[i]) / 2.0);
}
// Apply the shift using vtkImageChangeInformation.
vtkNew<vtkImageChangeInformation> changeInformation;
changeInformation->SetInputConnection(reader->GetOutputPort());
// Center the image by shifting it by the negative center coordinates.
changeInformation->SetOriginTranslation(-centroid[0], -centroid[1],
-centroid[2]);
// An outline provides context around the data.
vtkNew<vtkOutlineFilter> outlineFilter;
outlineFilter->SetInputConnection(changeInformation->GetOutputPort());
// We need to transform the outline so that it outlines the head.
auto transform = vtkSmartPointer<vtkTransform>::New();
// Move the outline from the origin to the new center inside the head.
transform->Translate(-centroid[0] * 2.0, -centroid[1] / 32.0,
-centroid[2] * 2.0);
// Apply the transform to the geometry
auto transformFilter = vtkSmartPointer<vtkTransformFilter>::New();
transformFilter->SetInputConnection(outlineFilter->GetOutputPort());
transformFilter->SetTransform(transform);
transformFilter->Update();
vtkNew<vtkPolyDataMapper> outlineMapper;
outlineMapper->SetInputConnection(transformFilter->GetOutputPort());
vtkNew<vtkProperty> outlineProperty;
outlineProperty->SetDiffuseColor(colors->GetColor3d("Black").GetData());
vtkNew<vtkActor> outline;
outline->SetMapper(outlineMapper);
outline->SetProperty(outlineProperty);
ren->AddActor(outline);
// The volume will be displayed by ray-cast alpha compositing.
// A ray-cast mapper is needed to do the ray-casting.
vtkNew<vtkFixedPointVolumeRayCastMapper> volumeMapper;
volumeMapper->SetInputConnection(changeInformation->GetOutputPort());
// The color transfer function maps voxel intensities to colors.
// It is modality-specific, and often anatomy-specific as well.
// The goal is to one color for flesh (between 500 and 1000)
// and another color for bone (1150 and over).
vtkNew<vtkColorTransferFunction> volumeColor;
volumeColor->AddRGBPoint(0, 0.0, 0.0, 0.0);
volumeColor->AddRGBPoint(500, 240.0 / 255.0, 184.0 / 255.0, 160.0 / 255.0);
volumeColor->AddRGBPoint(1000, 240.0 / 255.0, 184.0 / 255.0, 160.0 / 255.0);
volumeColor->AddRGBPoint(1150, 1.0, 1.0, 240.0 / 255.0); // Ivory
// The opacity transfer function is used to control the opacity
// of different tissue types.
vtkNew<vtkPiecewiseFunction> volumeScalarOpacity;
volumeScalarOpacity->AddPoint(0, 0.00);
volumeScalarOpacity->AddPoint(500, 0.15);
volumeScalarOpacity->AddPoint(1000, 0.15);
volumeScalarOpacity->AddPoint(1150, 0.85);
// The gradient opacity function is used to decrease the opacity
// in the "flat" regions of the volume while maintaining the opacity
// at the boundaries between tissue types. The gradient is measured
// as the amount by which the intensity changes over unit distance.
// For most medical data, the unit distance is 1mm.
vtkNew<vtkPiecewiseFunction> volumeGradientOpacity;
volumeGradientOpacity->AddPoint(0, 0.0);
volumeGradientOpacity->AddPoint(90, 0.5);
volumeGradientOpacity->AddPoint(100, 1.0);
// The VolumeProperty attaches the color and opacity functions to the
// volume, and sets other volume properties. The interpolation should
// be set to linear to do a high-quality rendering. The ShadeOn option
// turns on directional lighting, which will usually enhance the
// appearance of the volume and make it look more "3D". However,
// the quality of the shading depends on how accurately the gradient
// of the volume can be calculated, and for noisy data the gradient
// estimation will be very poor. The impact of the shading can be
// decreased by increasing the Ambient coefficient while decreasing
// the Diffuse and Specular coefficient. To increase the impact
// of shading, decrease the Ambient and increase the Diffuse and Specular.
vtkNew<vtkVolumeProperty> volumeProperty;
volumeProperty->SetColor(volumeColor);
volumeProperty->SetScalarOpacity(volumeScalarOpacity);
volumeProperty->SetGradientOpacity(volumeGradientOpacity);
volumeProperty->SetInterpolationTypeToLinear();
volumeProperty->ShadeOn();
volumeProperty->SetAmbient(0.4);
volumeProperty->SetDiffuse(0.6);
volumeProperty->SetSpecular(0.2);
// The vtkVolume is a vtkProp3D (like a vtkActor) and controls the position
// and orientation of the volume in world coordinates.
vtkNew<vtkVolume> volume;
volume->SetMapper(volumeMapper);
volume->SetProperty(volumeProperty);
// Finally, add the volume to the renderer
ren->AddViewProp(volume);
// Set up an initial view of the volume. The focal point will be the
// center of the volume, and the camera position will be 400mm to the
// patient's left (which is our right).
vtkCamera* camera = ren->GetActiveCamera();
camera->SetViewUp(0, 0, -1);
camera->SetPosition(-400, 0, 0);
camera->SetFocalPoint(0, 0, 0);
camera->ComputeViewPlaneNormal();
camera->Elevation(30);
camera->Dolly(1.5);
ren->ResetCameraClippingRange();
// Interact with the data.
renWin->Render();
// Important: The interactor must be set prior to enabling the widget.
auto category = "rlpais";
auto cow = MakeCameraOrientationWidget(ren, category, 1);
cow->On();
iren->Initialize();
iren->Start();
return EXIT_SUCCESS;
}
namespace {
TAxesParams DefineAxesLabels()
{
// clang-format off
return {
// Labels are: Anterior, Posterior, Dorsal, Ventral, Left, Right
{"apdvlr", {{"+X", "A"},{"-X", "P"},{"+Y", "D"},{"-Y", "V"},{"+Z", "L"},{"-Z", "R"}}},
{"apdvrl",{{"+X", "A"},{"-X", "P"},{"+Y", "D"},{"-Y", "V"},{"+Z", "R"},{"-Z", "L"}}},
{"aprlvd",{{"+X", "A"},{"-X", "P"},{"+Y", "R"},{"-Y", "L"},{"+Z", "V"},{"-Z", "D"}}},
{"padvlr", {{"+X", "P"},{"-X", "A"},{"+Y", "D"},{"-Y", "V"},{"+Z", "L"},{"-Z", "R"}}},
// Labels are: Left, Right, Superior, Inferior, Anterior, Posterior
{"lrsiap",{{"+X", "L"},{"-X", "R"},{"+Y", "S"},{"-Y", "I"},{"+Z", "A"},{"-Z", "P"}}},
{"lrpasi",{{"+X", "L"},{"-X", "R"},{"+Y", "P"},{"-Y", "A"},{"+Z", "S"},{"-Z", "I"}}},
{"rlpais",{{"+X", "R"},{"-X", "L"},{"+Y", "P"},{"-Y", "A"},{"+Z", "I"},{"-Z", "S"}}},
// Default labels.
{"xyz", {}},
};
// clang-format on
}
TAxesParams DefineAxesColors()
{
// clang-format off
TAxisParams color1{{"+X", "IndianRed"},{"-X", "FireBrick"},
{"+Y", "LimeGreen"},{"-Y", "DarkGreen"},
{"+Z", "Blue"}, {"-Z", "SteelBlue"}};
return {
{"apdvlr",color1},
{"apdvrl",color1},
{"aprlvd",color1},
{"padvlr",color1},
{"lrsiap",color1},
// Default colors.
{"lrpasi", {}},
{"rlpais", {}},
{"xyz", {}},
};
// clang-format on
}
std::map<std::string, std::pair<TAxisParams, TAxisParams>> GetAxesParams()
{
vtkNew<vtkNamedColors> colors;
// The keys must be the same.
auto axesLabels = DefineAxesLabels();
auto axesColors = DefineAxesColors();
// Get the keys.
std::set<std::string> labelKeys;
for (auto&& label : axesLabels)
{
labelKeys.insert(label.first);
}
std::set<std::string> colorKeys;
for (auto&& label : axesLabels)
{
colorKeys.insert(label.first);
}
std::vector<std::string> commonKeys;
std::set_intersection(labelKeys.begin(), labelKeys.end(), colorKeys.begin(),
colorKeys.end(), std::back_inserter(commonKeys));
std::map<std::string, std::pair<TAxisParams, TAxisParams>> alc;
for (auto&& k : commonKeys)
{
alc[k] = std::pair<TAxisParams, TAxisParams>{axesLabels[k], axesColors[k]};
}
return alc;
};
vtkNew<vtkCameraOrientationWidget>
MakeCameraOrientationWidget(vtkRenderer* ren, std::string alcKey,
int const& position)
{
vtkNew<vtkCameraOrientationWidget> cow;
cow->SetParentRenderer(ren);
cow->EnabledOn();
auto axesParameters = GetAxesParams();
std::set<std::string> keys;
for (auto&& label : axesParameters)
{
keys.insert(label.first);
}
auto it = keys.find(alcKey);
if (it == keys.end())
{
std::string res =
"Invalid key for axes labels and colors.\nValid keys are: ";
for (const auto& [key, value] : axesParameters)
{
res += fmt::format("{:s}, ", key);
}
if (res.length() >= 2)
{
auto pos = res.length() - 2;
res.replace(pos, 2, "");
}
res += "\nUsing the key: xyz";
std::cout << res << std::endl;
alcKey = "xyz";
}
auto alc = axesParameters[alcKey];
vtkNew<vtkCameraOrientationRepresentation> rep;
switch (position)
{
case 0:
rep->AnchorToLowerLeft();
break;
case 1:
rep->AnchorToUpperLeft();
break;
case 2:
rep->AnchorToLowerRight();
break;
default:
rep->AnchorToUpperRight();
}
if (!alc.first.empty())
{
rep->SetXPlusLabelText(alc.first["+X"]);
rep->SetXMinusLabelText(alc.first["-X"]);
rep->SetYPlusLabelText(alc.first["+Y"]);
rep->SetYMinusLabelText(alc.first["-Y"]);
rep->SetZPlusLabelText(alc.first["+Z"]);
rep->SetZMinusLabelText(alc.first["-Z"]);
}
if (!alc.second.empty())
{
vtkNew<vtkNamedColors> colors;
rep->SetXAxisColor(colors->GetColor3d(alc.second["+X"]).GetData());
rep->SetYAxisColor(colors->GetColor3d(alc.second["+Y"]).GetData());
rep->SetZAxisColor(colors->GetColor3d(alc.second["+Z"]).GetData());
}
cow->SetRepresentation(rep);
cow->Off();
return cow;
}
} // namespace
CMakeLists.txt¶
cmake_minimum_required(VERSION 3.12 FATAL_ERROR)
project(MedicalDemo4)
find_package(VTK COMPONENTS
CommonColor
CommonCore
CommonDataModel
IOImage
InteractionStyle
RenderingContextOpenGL2
RenderingCore
RenderingFreeType
RenderingGL2PSOpenGL2
RenderingOpenGL2
RenderingVolume
RenderingVolumeOpenGL2
)
if (NOT VTK_FOUND)
message(FATAL_ERROR "MedicalDemo4: Unable to find the VTK build folder.")
endif()
# Prevent a "command line is too long" failure in Windows.
set(CMAKE_NINJA_FORCE_RESPONSE_FILE "ON" CACHE BOOL "Force Ninja to use response files.")
add_executable(MedicalDemo4 MACOSX_BUNDLE MedicalDemo4.cxx )
target_link_libraries(MedicalDemo4 PRIVATE ${VTK_LIBRARIES}
)
# vtk_module_autoinit is needed
vtk_module_autoinit(
TARGETS MedicalDemo4
MODULES ${VTK_LIBRARIES}
)
Download and Build MedicalDemo4¶
Click here to download MedicalDemo4 and its CMakeLists.txt file. Once the tarball MedicalDemo4.tar has been downloaded and extracted,
cd MedicalDemo4/build
If VTK is installed:
cmake ..
If VTK is not installed but compiled on your system, you will need to specify the path to your VTK build:
cmake -DVTK_DIR:PATH=/home/me/vtk_build ..
Build the project:
make
and run it:
./MedicalDemo4
WINDOWS USERS
Be sure to add the VTK bin directory to your path. This will resolve the VTK dll's at run time.