Hello,
We have an existing application that uses VTK 9.5.2 for our 3D visualization. We have noticed that when we load large 2D images (23,500 x 23,500 pixels) that the interaction rendering speed on MacOS is about 3~4 frames per second. @sankhesh provided a simple pure VTK example code (which I’ll throw at the bottom) that essentially either loads the image or just creates a large 2D image. If we compile that code on Linux (Ubuntu 22.04) or Windows 11 against Vtk 9.5.2 then the rendering speed is 1000 FPS. Here are the machine specs:
MacOS Sonoma 14.8.2, XCode 16.2, 64GB Ram 16 core M3 Max CPU.
Windows: AMD 9950, AMD Radeon 9060XT, 128GB Ram
Linux: AMD 9950, NVidia 3060 (12GB), 128GB RAM
I was thinking maybe it just is the MacOS machine but then I loaded up ParaView 6.0.0 and loaded the same data set and rendered it using the “Slice” view. It renders at what I would assume is 1000 FPS. So I know it isn’t the MacOS machine specifically but how we are initializing or setting up the Vtk Code.
Has anyone run into this before? Would anyone have any ideas? If there are any ParaView engineers lurking about, could you point us to how ParaView sets up the Slice view so we can try to duplicate that setup?
We compiled against Vtk 9.5.0, 9.5.1, 9.5.2, 9.6.0.rc3
The data set is located here: Vkt_Demo_Data - Google Drive
Code Example
#include <vtkImageActor.h>
#include <vtkImageData.h>
#include <vtkImageProperty.h>
#include <vtkImageSliceMapper.h>
#include <vtkInteractorStyleTrackballCamera.h>
#include <vtkLookupTable.h>
#include <vtkNew.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkRenderer.h>
#include <vtkSmartPointer.h>
#include <vtkTIFFReader.h>
#include <vtkActor.h>
#include <vtkCallbackCommand.h>
#include <vtkNamedColors.h>
#include <vtkNew.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkRenderer.h>
#include <vtkSphereSource.h>
#include <iostream>
#include <string>
namespace {
void CallbackFunction(vtkObject* caller, long unsigned int eventId,
void* clientData, void* callData);
}
int main(int argc, char* argv[])
{
vtkSmartPointer<vtkImageData> imageData;
if (argc > 1)
{
vtkNew<vtkTIFFReader> reader;
reader->SetFileName(argv[1]);
reader->Update();
imageData = reader->GetOutput();
}
else
{
const int width = 23500;
const int height = 23500;
imageData = vtkSmartPointer<vtkImageData>::New();
imageData->SetDimensions(width, height, 1);
imageData->AllocateScalars(VTK_UNSIGNED_CHAR, 1);
for (int y = 0; y < height; ++y)
{
for (int x = 0; x < width; ++x)
{
unsigned char* pixel = static_cast<unsigned char*>(imageData->GetScalarPointer(x, y, 0));
// Create a checkerboard pattern
if (((x / 100) % 2) == ((y / 100) % 2))
{
pixel[0] = 255;
}
else
{
pixel[0] = 0;
}
}
}
}
double scRange[2];
imageData->GetScalarRange(scRange);
// Create a lookup table to map scalar values to colors
vtkNew<vtkLookupTable> lookupTable;
lookupTable->SetNumberOfTableValues(256);
lookupTable->SetRange(scRange);
lookupTable->Build();
// Map 0 to black, 255 to red, others to grayscale
for (int i = 0; i < 256; ++i)
{
if (i == 0)
lookupTable->SetTableValue(i, 0.0, 0.0, 0.0, 1.0); // black
else if (i == 255)
lookupTable->SetTableValue(i, 1.0, 1.0, 1.0, 1.0); // white
else
lookupTable->SetTableValue(i, i / 255.0, i / 255.0, i / 255.0, 1.0); // grayscale
}
vtkNew<vtkImageSliceMapper> imageMapper;
imageMapper->SetInputData(imageData);
vtkNew<vtkImageActor> actor;
actor->GetProperty()->SetLookupTable(lookupTable);
actor->SetMapper(imageMapper);
vtkNew<vtkRenderer> renderer;
renderer->AddActor(actor);
vtkNew<vtkRenderWindow> renderWindow;
renderWindow->AddRenderer(renderer);
vtkNew<vtkRenderWindowInteractor> renderWindowInteractor;
renderWindowInteractor->SetRenderWindow(renderWindow);
vtkNew<vtkCallbackCommand> callback;
callback->SetCallback(CallbackFunction);
renderer->AddObserver(vtkCommand::EndEvent, callback);
vtkNew<vtkInteractorStyleTrackballCamera> style;
renderWindowInteractor->SetInteractorStyle(style);
renderWindow->Render();
renderWindowInteractor->Start();
return EXIT_SUCCESS;
}
namespace {
void CallbackFunction(vtkObject* caller, long unsigned int vtkNotUsed(eventId),
void* vtkNotUsed(clientData), void* vtkNotUsed(callData))
{
vtkRenderer* renderer = static_cast<vtkRenderer*>(caller);
double timeInSeconds = renderer->GetLastRenderTimeInSeconds();
double fps = 1.0 / timeInSeconds;
std::cout << "FPS: " << fps << std::endl;
//std::cout << "Callback" << std::endl;
}
} // namespace
Required Large Data Patch
You will also need to have this patch applied to your VTK sources in order to load an image this large. I think this is patched into VTK Master at this point.
diff --git a/Rendering/Core/vtkImageMapper3D.cxx b/Rendering/Core/vtkImageMapper3D.cxx
index 99471c7531..39d43364f9 100644
--- a/Rendering/Core/vtkImageMapper3D.cxx
+++ b/Rendering/Core/vtkImageMapper3D.cxx
@@ -836,7 +836,8 @@ unsigned char* vtkImageMapper3D::MakeTextureData(vtkImageProperty* property, vtk
// could not directly use input data, so allocate a new array
reuseData = false;
- unsigned char* outPtr = new unsigned char[ysize * xsize * bytesPerPixel];
+ unsigned char* outPtr =
+ new unsigned char[static_cast<size_t>(ysize) * static_cast<size_t>(xsize) * bytesPerPixel];
// output increments
vtkIdType outIncY = bytesPerPixel * (xsize - imageSize[0]);
If you read this far, thank you. Any help, pointers or gentle nudges in a direction are appreciated.
Mike Jackson