How to get gradual transition between discrete LUT colors?

I am writing an app with VTK-9.3.0 to display seafloor bathymetry. I would like to assign a colormap so that color varies between discrete color values in a gradual way as shown in the figure on the left, using discrete values in the “Haxby” colormap, which contains 11 colors.

The figure on the left shows bathymetry displayed by someone else’s C/X11 app; I don’t know the details of how the colormap is applied. I want my app’s display to look like this also. The figure on the right shows the same bathymetry as currently displayed by my app, and there is no gradation between colors. How to achieve that gradation?

Following this vtkColorLookupTable example, I build the Haxby LUT as follows:

 const int NMapColors = 11;

  const float haxbyRed[NMapColors] =
    {0.950, 1.000, 1.000, 1.000, 0.941, 0.804, 0.541, 0.416, 0.196,
     0.157, 0.145};

  const float haxbyGreen[NMapColors] =
   {0.950, 0.729, 0.631, 0.741, 0.925, 1.000, 0.925, 0.922, 0.745,
    0.498, 0.224};
  
  const float haxbyBlue[NMapColors] =
   {0.950, 0.522, 0.267, 0.341, 0.475, 0.635, 0.682, 1.000, 1.000,
    0.984, 0.686};

  vtkSmartPointer<vtkNamedColors> colors =
    vtkSmartPointer<vtkNamedColors>::New();

    vtkSmartPointer<vtkColorSeries> colorSeries =
      vtkSmartPointer<vtkColorSeries>::New();

    char nameBuf[16];
    colorSeries->SetNumberOfColors(nColors);
    for (int i = 0; i < NMapColors; i++) {
      sprintf(nameBuf, "color-%d", i);
      vtkStdString name(nameBuf);
      colors->SetColor(name, haxbyRed[nColors-i], haxbyGreen[nColors-i],
                       haxbyBlue[nColors-i]);
      colorSeries->SetColor(i, colors->GetColor3ub(name));
    }
    
    colorSeries->BuildLookupTable(lut, colorSeries->ORDINAL);
    lut->SetRampToLinear();

I assumed that lut->SetRampToLinear() would achieve the color gradation I’d like, but it does not. I also tried SetRampToSCurve() and SetRampToSQRT(), to no effect.
How can I achieve gradual color transition? Thanks!

I think you can follow this example: https://kitware.github.io/vtk-examples/site/Cxx/Rendering/Rainbow/
specifically the “high contrast lut” section, which fills the LUT using SetTableValue(). When you use vtkColorSeries, you are telling it to use categorical colors, so you get the bands you see.

You might also try setting IndexedLookup to false on your LUT, but I’m not sure that will work.

1 Like

@Aron_Helser - thanks, I’ve changed my code to follow the Rainbow ‘high contrast lut’ example, but result is the same, with categorical color bands. I call lut->SetIndexedLookup(false) and still get the same result. My code currently look like this:

    lut->SetRampToSCurve();
    lut->SetNumberOfTableValues(nColors);
    lut->SetTableRange(-2100., 0.);
    lut->SetIndexedLookup(false);
    
    for (vtkIdType i = 0; i < nColors; i++) {
      int ind = nColors - 1 - i;
      lut->SetTableValue(ind, haxbyRed[ind], haxbyGreen[ind], haxbyBlue[ind],
                         0.8);
    }

    lut->Build();

Well, that’s not what I expected, but I found another example that uses vtkColorTransferFunction to explicitly do the color interpolation. Specifically GetDivergingLut1(), but you would not want to call SetColorSpaceToDiverging(). I haven’t tracked down if this is what ParaView does…
https://kitware.github.io/vtk-examples/site/Cxx/PolyData/CurvaturesDemo/

Here’s another example with a simpler use:
https://kitware.github.io/vtk-examples/site/Cxx/PolyData/Curvatures/

1 Like

Without seeing the details, it sure looks like you are coloring via cell data, and not point data. Point coloring provides a much smoother transition…

1 Like

Ah @Aron_Helser thank you! Using vtkColorTransferFunction (following the Curvatures Demo) results in gradual color transition!
However there is a white band appearing in about the middle of the color range. Do I need to specify midpoint and sharpness arguments in my call to ctf->AddRGBPoint(), and if so how are those calculated?

 const int NMapColors = 11;

  const float haxbyRed[NMapColors] =
    {0.950, 1.000, 1.000, 1.000, 0.941, 0.804, 0.541, 0.416, 0.196,
     0.157, 0.145};

  const float haxbyGreen[NMapColors] =
   {0.950, 0.729, 0.631, 0.741, 0.925, 1.000, 0.925, 0.922, 0.745,
    0.498, 0.224};
  
  const float haxbyBlue[NMapColors] =
   {0.950, 0.522, 0.267, 0.341, 0.475, 0.635, 0.682, 1.000, 1.000,
    0.984, 0.686};

    vtkNew<vtkColorTransferFunction> ctf;
    ctf->SetColorSpaceToDiverging();
    
    for (int i = 0; i < NMapColors; i++) {
      // x ranges from 0. (i=0) to 1. (i=nColors-1)
      double x = (double )i / (double )(nColors - 1);
      int ind = NMapColors-1 - i;   // Start mapping from end of haxby color arrays
      ctf->AddRGBPoint(x, haxbyRed[ind], haxbyGreen[ind], haxbyBlue[ind]);
    }

    auto tableSize = 256;
    lut->SetNumberOfTableValues(tableSize);
    lut->Build();
    for (int i = 0; i < lut->GetNumberOfColors(); i++) {
      std::array<double, 3> rgb;
      ctf->GetColor(static_cast<double>(i) / lut->GetNumberOfColors(),
                    rgb.data());
      std::array<double, 4> rgbAlpha{0., 0., 0., 1.0};
      std::copy(std::begin(rgb), std::end(rgb), std::begin(rgbAlpha));
      lut->SetTableValue(static_cast<vtkIdType>(i), rgbAlpha.data());
    }

Remove this line, it is causing the white band: ctf->SetColorSpaceToDiverging();

1 Like

Works like a charm now - thank you!