demystifiying color discretization

Hi,

In the past, I had a slow rendering problem. At the time, the problem was color management with ‘vtkColorTransferFunction’.

I solved the problem by using a ‘vtkLookupTable’ along with the ‘vtkColorTransferFunction’, example :

// I add control points to the vtkColorTransferFunction
for (const auto& pt : ctrlPts)
{
    m_transferFunction->AddRGBPoint(pt[0], pt[1], pt[2], pt[3]);
}

/* I build the lut */
m_lookupTable->SetNumberOfTableValues(m_lookupTableSize); //  e.g. m_lookupTableSize == 256
m_lookupTable->SetRange(minRange, maxRange); // the range of data retrieved from a vtkImageData
m_lookupTable->Build();

double rgb[3];
for (size_t i = 0; i < m_lookupTableSize; ++i)
{
    m_transferFunction->GetColor(static_cast<double>(i) / m_lookupTableSize, rgb);
    m_lookupTable->SetTableValue(i, rgb[0], rgb[1], rgb[2]);
    // in the VTK examples, there is a memory bug, do not feed rgb a ptr to SetTableValue
} 

Recently, I encountered a class named vtkDiscretizableColorTransferFunction, that is also wrapped in ParaView under the name vtkPVDiscretizableColorTransferFunction.

I thought this last is doing what I am doing in the code I posted above, but when I tested it, it was slow as hell, just like ‘vtkColorTransferFunction’.

Strangely, in Paraview (with a 256 LUT sized table for example), rendering didn’t seem slow, may be I am using it wrong. Example :

    vtkNew<vtkDiscretizableColorTransferFunction> discretTF;
    discretTF->DiscretizeOn();
    discretTF->SetNumberOfValues(256);
    discretTF->IndexedLookupOff();
    discretTF->AddRGBPoint(0,   0.23137254902,  0.298039215686,  0.752941176471);
    discretTF->AddRGBPoint(0.5, 0.865,          0.865,           0.865);
    discretTF->AddRGBPoint(1,   0.705882352941, 0.0156862745098, 0.149019607843);
    discretTF->Build();

When I render a vtkImageData with the code above, it’s as slow as using vtkColorTransferFunction (not discretized I guess).

My questions are :

  • If we want discretized colors (to provide the fastest rendering), what’s the best strategy ?
  • Why vtkDiscretizableColorTransferFunction (or even vtkPVDiscretizableColorTransferFunction, I took the class from ParaView and it has the same slow rendering time as vtkDiscretizableColorTransferFunction) is slow ?

Thanks.

Can you run the code using a profiler to see where most of the time is spent?

If you use Visual Studio then you can use the built-in profiler. You can also simply run VerySleepy profiler and attach it to the running process. To get line numbers and realistic performance, set VTK build mode to “RelWithDebInfo”.

I’m working under Linux CentOS. I’m using a simple C++ program to read data and measure rendering times (the Render() instruction).

If I use non discretized colors (a vtkColorTransferFunction as a transfer function fed to vtkImageMapToColors - 3D rendering - or even a 2D vtkChartHistogram2D) or a vtkDiscretizableColorTransferFunction, the rendering time is 9 - 10 second (in total for many vtkImageData(s))

Whereas with a vtkLookupTable (and a vtkColorTransferFunction to fill the LUT), the rendering time is 3 seconds.

I can’t test under Visual Studio, for now. I don’t know how to profile a code under Linux.

In ParaView, I don’t know how vtkPVDiscretizableColorTransferFunction is being used as there’s many layers between the class and the GUI. But it renders fast the result (when Discretize checkbox is checked).

It is a skill that is worth learning. You should be able to find lots of tutorials on the web.

OK, I will profile it when I find free time.

I profiled the application using valgrind and I noticed that vtkDiscretizableColorTransferFunction’s “Build”, “MTime” and “GetColor” methods are being called many times (for my example respectively 174 097 665, 174 097 664 and 174 097 663). And as this class uses vtkLookupTable, there’s 3 methods that are called also 174 million times…

That’s strange as in my code “Build” is called only one time with the first vtkImageData I render (to retrieve the data range from this last and use it when adding RGB points) :

        static bool s_bLUTInit = false;
        if (!s_bLUTInit)
        {
            double range[2];
            imgData->GetPointData()->GetArray(0)->GetRange(range);

            discretTF->DiscretizeOn();
            discretTF->SetNumberOfValues(256);
            discretTF->IndexedLookupOff();
            const double scale = range[1] - range[0];
            discretTF->AddRGBPoint(0* scale + range[0],   0.23137254902,  0.298039215686,  0.752941176471);
            discretTF->AddRGBPoint(0.5* scale + range[0], 0.865,          0.865,           0.865);
            discretTF->AddRGBPoint(1* scale + range[0],   0.705882352941, 0.0156862745098, 0.149019607843);
            discretTF->Build();
            chart->SetTransferFunction(discretTF.Get());

            s_bLUTInit = true;
        } 

And to refresh the chart (e.g. a vtkChartHistogram2D), I just call

chart->SetInputData(imgData);
chart->RecalculateBounds();
pRenderWindow->Render();

The code above is called in a loop, where I retrieve from a reader a vtkImageData.

To sum up, with the first code I posted, I don’t have a slow rendering (vtkLookupTable + vtkColorTransferFunction), but when using vtkDiscretizableColorTransferFunction the rendering is very slow as each time when I change the vtkImageData fed to the vtkChartHistogram2D (or even a vtkImageActor + vtkImageMapToColors), the vtkDiscretizableColorTransferFunction is rebuilt again and again.

How to prevent that constant rebuilding ? why I don’t have this issue when using vtkLookupTable (first code) ?

You can put a breakpoint in Build method and see why it is called (you can step back in the stack trace to see what method called it and why).

The build method is called, but as BuildTime > GetMTime() it returns immediately. So the problem is elsewhere.

I noticed that with vtkDiscretizable…, the method used to map values is GetColor, this last use MapValue(v) many times 174 millions times etc… callstack :

 1   vtkLookupTable::MapValue                          vtkLookupTable.cxx           707  0x7fc651a2984d 
2   vtkLookupTable::GetColor                          vtkLookupTable.cxx           352  0x7fc651a1012d 
3   vtkColorTransferFunctionMapData<char>             vtkColorTransferFunction.cxx 1364 0x7fc65560c4bd 
4   vtkColorTransferFunction::MapScalarsThroughTable2 vtkColorTransferFunction.cxx 1706 0x7fc6555fb254 
5   vtkPlotHistogram2D::GenerateHistogram             vtkPlotHistogram2D.cxx       265  0x484480       
6   vtkPlotHistogram2D::Update                        vtkPlotHistogram2D.cxx       44   0x48355e       
7   vtkChartHistogram2D::Update                       vtkChartHistogram2D.cxx      58   0x7fc65a886f74 
8   vtkChartXY::Paint                                 vtkChartXY.cxx               385  0x7fc65a8957bd 
9   PaintItems                                        vtkContextScenePrivate.h     89   0x7fc65a60e3fd 
10  vtkContextScene::Paint                            vtkContextScene.cxx          125  0x7fc65a60e3fd 
11  vtkContextActor::RenderOverlay                    vtkContextActor.cxx          220  0x7fc65a6088ee 
12  vtkOpenGLContextActor::RenderOverlay              vtkOpenGLContextActor.cxx    73   0x7fc660903eb9 
13  vtkOpenGLRenderer::UpdateGeometry                 vtkOpenGLRenderer.cxx        262  0x7fc65be14f2f 
14  vtkOpenGLRenderer::DeviceRender                   vtkOpenGLRenderer.cxx        166  0x7fc65be14330 
15  vtkRenderer::Render                               vtkRenderer.cxx              338  0x7fc65569f644 
16  vtkRendererCollection::Render                     vtkRendererCollection.cxx    51   0x7fc65569d77a 
17  vtkRenderWindow::DoStereoRender                   vtkRenderWindow.cxx          768  0x7fc6556af1a5 
18  vtkRenderWindow::DoFDRender                       vtkRenderWindow.cxx          737  0x7fc6556afdb7 
19  vtkRenderWindow::DoAARender                       vtkRenderWindow.cxx          616  0x7fc6556af9be 
20  vtkRenderWindow::Render                           vtkRenderWindow.cxx          432  0x7fc6556b0db3 
... <More>                                                                                             

Whereas with the solution I made, it’s the vtkLookupTable::MapScalarsThroughTable2 that is used, and is only called 137 times. callstack :

1  vtkLookupTable::MapScalarsThroughTable2 vtkLookupTable.cxx         1142 0x7f9e8564d943 
2  vtkPlotHistogram2D::GenerateHistogram   vtkPlotHistogram2D.cxx     265  0x48427e       
3  vtkPlotHistogram2D::Update              vtkPlotHistogram2D.cxx     44   0x48335c       
4  vtkChartHistogram2D::Update             vtkChartHistogram2D.cxx    58   0x7f9e8e4a9f74 
5  vtkChartXY::Paint                       vtkChartXY.cxx             385  0x7f9e8e4b87bd 
6  PaintItems                              vtkContextScenePrivate.h   89   0x7f9e8e2313fd 
7  vtkContextScene::Paint                  vtkContextScene.cxx        125  0x7f9e8e2313fd 
8  vtkContextActor::RenderOverlay          vtkContextActor.cxx        220  0x7f9e8e22b8ee 
9  vtkOpenGLContextActor::RenderOverlay    vtkOpenGLContextActor.cxx  73   0x7f9e94526eb9 
10 vtkOpenGLRenderer::UpdateGeometry       vtkOpenGLRenderer.cxx      262  0x7f9e8fa37f2f 
11 vtkOpenGLRenderer::DeviceRender         vtkOpenGLRenderer.cxx      166  0x7f9e8fa37330 
12 vtkRenderer::Render                     vtkRenderer.cxx            338  0x7f9e892c2644 
13 vtkRendererCollection::Render           vtkRendererCollection.cxx  51   0x7f9e892c077a 
14 vtkRenderWindow::DoStereoRender         vtkRenderWindow.cxx        768  0x7f9e892d21a5 
15 vtkRenderWindow::DoFDRender             vtkRenderWindow.cxx        737  0x7f9e892d2db7 
16 vtkRenderWindow::DoAARender             vtkRenderWindow.cxx        616  0x7f9e892d29be 
17 vtkRenderWindow::Render                 vtkRenderWindow.cxx        432  0x7f9e892d3db3 
18 vtkXOpenGLRenderWindow::Render          vtkXOpenGLRenderWindow.cxx 1856 0x7f9e8fae9ce1 
19 main                                    main.cpp                   642  0x466c21

I am not sure maybe ParaView is using effectively a vtkLookupTable under the hood. But the question remains, the vtkDiscretizable… is named discretizable but in fact, it doesn’t provide that feature as it’s slow… If someone can demystify to me this crazy stuff, that would be great !

Up (I really want to clarify the subject)…

I found the answer : in the older versions of VTK, MapScalarsThroughTable2 is not overriden for vtkDiscretizableColorTransferFunction, thus it uses the vtkColorTransferFunction version. it must be overriden to use the MapScalarsThroughTable2 of its member LookupTable (vtkLookupTable) when Discretize mode is enabled.

1 Like