Skip to content

RescaleReverseLUT

Repository source: RescaleReverseLUT


Description

This example shows how to adjust a colormap so that the colormap scalar range matches the scalar range on the object. This is done by adjusting the colormap so that the colormap scalar range matches the scalar range of the object by rescaling the control points and, optionally, reversing the order of the colors.

Here, we generate the original Color Transfer Function (CTF) corresponding to the seven modern rainbow colors or the colors Isaac Newton labeled when dividing the spectrum of visible light in 1672. The scalar range in the CTF is [-1.0, 1.0].

The triangle has a vtkElevationFilter applied to it with a scalar range of [-0.5, 0.5].

There are four images:

  • Original - The triangle is colored by only the top five colors from the CTF. This is because the elevation scalar range on the triangle is [-0.5, 0.5] and the CTF scalar range is [-1.0, 1.0]. So the coloring is orange->blue corresponding to the range [-0.5, 0.5].
  • Reversed - We create a new CTF from the original CTF just by reversing the colors. The lower five colors in the original CTF are now the top five colors used to color the triangle. The coloring is now blue->orange.
  • Rescaled - We create a new CTF by rescaling the original CTF to the range [-0.5, 0.5] matching the scalar range of the elevation filter. The coloring is red->violet.
  • Rescaled and Reversed - A new CTF is created by reversing the rescaled CTF.The coloring is now violet->red.

Two options are provided:

  • -c: Use a continuous color distribution instead of a discretized one.
  • -r: Reverse the colors.

Other languages

See (Python), (PythonicAPI)

Question

If you have a question about this example, please use the VTK Discourse Forum

Code

RescaleReverseLUT.cxx

#include <vtkActor.h>
#include <vtkActor2D.h>
#include <vtkCamera.h>
#include <vtkConeSource.h>
#include <vtkDiscretizableColorTransferFunction.h>
#include <vtkElevationFilter.h>
#include <vtkInteractorStyleTrackballCamera.h>
#include <vtkNamedColors.h>
#include <vtkNew.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkRenderer.h>
#include <vtkScalarBarActor.h>
#include <vtkScalarBarRepresentation.h>
#include <vtkScalarBarWidget.h>
#include <vtkSmartPointer.h>
#include <vtkTextActor.h>
#include <vtkTextMapper.h>
#include <vtkTextProperty.h>
#include <vtkTextRepresentation.h>
#include <vtkTextWidget.h>

#include <vtk_cli11.h>
#include <vtk_fmt.h>
// clang-format off
#include VTK_FMT(fmt/format.h)
// clang-format on

namespace fs = std::filesystem;

namespace {
/**
 * @brief GetRainbowCTF Generate the color transfer function.
 *
 * The seven colors corresponding to the colors that Isaac Newton labeled
 *     when dividing the spectrum of visible light in 1672 are used.
 *
 * The modern variant of these colors is used by default.
 *
 * See: [Rainbow](https://en.wikipedia.org/wiki/Rainbow)
 *
 * @param modern: Selects either Newton's original seven colors or the modern
 * version.
 * @param discretize: Selects whether the CTF is discretized or not.
 * @param reverse: Reverse the colors in the CTF.
 *
 * @return The color transfer function.
 */
vtkNew<vtkDiscretizableColorTransferFunction>
GetRainbowCTF(bool const& modern = true, bool const& discretize = true,
              bool const& reverse = false);

/**
 * Rescale the values.
 *
 * See:
 * https://stats.stackexchange.com/questions/25894/changing-the-scale-of-a-variable-to-0-100
 *
 * @param xv: The values to be rescaled.
 * @param newMin: The new minimum value.
 * @param newMax: The new maximum value.
 *
 * @return The rescaled values.
 */
std::vector<double> Rescale(std::vector<double> const& xv,
                            double const& newMin = 0, double const& newMax = 1);

/**
 * Rescale and, optionally, reverse the colors in the color transfer function.
 *
 * @param ctf: The color transfer function to rescale.
 * @param newMin: The new minimum value.
 * @param newMax: The new maximum value.
 * @param discretize: Selects whether the CTF is discretized or not.
 * @param reverse: If true, reverse the colors.
 *
 * @return The rescaled color transfer function.
 */
vtkNew<vtkDiscretizableColorTransferFunction>
RescaleCTF(vtkNew<vtkDiscretizableColorTransferFunction> const& ctf,
           double const& newMin = 0, double const& newMax = 1);

typedef std::map<std::string, std::array<double, 2>> TTextPosition;
typedef std::map<std::string, TTextPosition> TTextPositions;

struct ScalarBarProperties
{
  vtkSmartPointer<vtkNamedColors> colors;

  // The properties needed for scalar bars.
  vtkSmartPointer<vtkDiscretizableColorTransferFunction> lut;
  // These are in pixels.
  std::map<std::string, int> maximumDimensions{{"width", 100}, {"height", 260}};
  std::string titleText{""};
  int number_of_labels{7};
  std::string labelFormat{"{:0.2f}"};
  // Orientation vertical=true, horizontal=false.
  bool orientation{true};
  // Horizontal and vertical positioning.
  // These are the defaults, don't change these.
  TTextPosition defaultV = {{"p", {0.85, 0.1}}, {"p2", {0.1, 0.7}}};
  TTextPosition defaultH = {{"p", {0.10, 0.1}}, {"p2", {0.7, 0.1}}};
  // Modify these as needed.
  TTextPosition positionV = {{"p", {0.8, 0.15}}, {"p2", {0.1, 0.7}}};
  TTextPosition positionH = {{"p", {0.125, 0.05}}, {"p2", {0.75, 0.1}}};
};

/** Make a scalar bar widget.
 *
 *  @param scalar_bar_properties - The lookup table, title name, maximum
 * dimensions in pixels and position.
 *  @param textProperty - The properties for the title.
 *  @param labelTextProperty - The properties for the labels.
 *  @param ren - The vtkRenderer.
 *  @param iren - The vtkInteractor.
 *  @return The scalar bar widget.
 */
vtkNew<vtkScalarBarWidget>
MakeScalarBarWidget(ScalarBarProperties& scalarBarProperties,
                    vtkTextProperty* textProperty,
                    vtkTextProperty* labelTextProperty, vtkRenderer* ren,
                    vtkRenderWindowInteractor* iren);

/**
 * Get viewport positioning information for a vector of names.
 *
 * Note: You must include vtkSystemIncludes.h to get these defines:
 *  VTK_TEXT_LEFT 0, VTK_TEXT_CENTERED 1, VTK_TEXT_RIGHT 2,
 *  VTK_TEXT_BOTTOM 0, VTK_TEXT_TOP 2
 *
 *  @param names - The vector of names.
 *  @param justification - Horizontal justification of the text.
 *  @param verticalJustification - Vertical justification of the text.
 *  @param width - Width of the bounding_box of the text in screen coordinates.
 *  @param height - Height of the bounding_box of the text in screen
 * coordinates.
 *  @return A map of positioning information.
 */
TTextPositions
GetTextPositions(std::vector<std::string> const& names,
                 int const justification = VTK_TEXT_LEFT,
                 int const verticalJustification = VTK_TEXT_BOTTOM,
                 double const width = 0.96, double const height = 0.1);

/** Convert a string to title case.
 *
 *  @param input - The string to convert.
 *  @return The string converted to title case.
 */
std::string Title(const std::string& input);

} // namespace

int main(int argc, char* argv[])
{
  CLI::App app{
      "Demonstrates how to adjust the colormap scalar range and reverse it."};
  // Define options
  auto newtonian{false};
  app.add_flag("-n, --newton", newtonian,
               "Use the Newtonian colors colors instead of the modern colors.");
  auto continuous{false};
  app.add_flag("-c, --continuous", continuous, "Build a continuous colormap.");

  CLI11_PARSE(app, argc, argv);

  auto modern = !newtonian;
  auto discretize = !continuous;

  std::vector<std::string> ctfNames{"Modern Rainbow", "Newton's Rainbow"};
  std::string ctfName;
  if (modern)
  {
    ctfName = ctfNames[0];
  }
  else
  {
    ctfName = ctfNames[1];
  }

  vtkNew<vtkNamedColors> colors;

  //  RenderWindow Dimensions.
  auto renderWidth = 400;
  auto renderHeight = 400;
  auto gridDimensions = 2;
  auto windowWidth = renderWidth * gridDimensions;
  auto windowHeight = renderHeight * gridDimensions;

  vtkNew<vtkRenderWindow> renWin;
  renWin->SetSize(windowWidth, windowHeight);
  auto appFn = fs::path((app.get_name())).stem().string();
  renWin->SetWindowName(appFn.c_str());
  vtkNew<vtkRenderWindowInteractor> iRen;
  iRen->SetRenderWindow(renWin);

  vtkNew<vtkInteractorStyleTrackballCamera> style;
  iRen->SetInteractorStyle(style);

  // Define names.
  std::vector<std::string> names{"Orginal", "Rescaled", "Original Reversed",
                                 "Rescaled Reversed"};
  auto txtPos =
      GetTextPositions(names, VTK_TEXT_CENTERED, VTK_TEXT_BOTTOM, 0.5, 0.1);
  auto ctfNamePos =
      GetTextPositions(ctfNames, VTK_TEXT_LEFT, VTK_TEXT_TOP, 0.7, 0.1);

  // Create text properties.
  vtkNew<vtkTextProperty> textProperty;
  textProperty->SetColor(colors->GetColor3d("AliceBlue").GetData());
  textProperty->SetFontSize(12);
  textProperty->SetFontFamilyAsString("Courier");
  textProperty->BoldOn();
  textProperty->ItalicOff();
  textProperty->ShadowOn();
  textProperty->SetJustificationToCentered();
  textProperty->SetVerticalJustificationToCentered();

  vtkNew<vtkTextProperty> titleTextProperty;
  titleTextProperty->SetColor(colors->GetColor3d("AliceBlue").GetData());
  titleTextProperty->SetFontSize(12);
  titleTextProperty->BoldOn();
  titleTextProperty->ItalicOn();
  titleTextProperty->ShadowOff();
  titleTextProperty->SetJustificationToCentered();
  titleTextProperty->SetVerticalJustificationToCentered();

  vtkNew<vtkTextProperty> labelTextProperty;
  labelTextProperty->SetColor(colors->GetColor3d("AliceBlue").GetData());
  labelTextProperty->SetFontSize(12);
  labelTextProperty->BoldOff();
  labelTextProperty->ItalicOff();
  labelTextProperty->ShadowOff();
  labelTextProperty->SetJustificationToCentered();
  labelTextProperty->SetVerticalJustificationToCentered();

  vtkNew<vtkConeSource> source;
  source->SetCenter(0.0, 0.0, 0.0);
  source->SetResolution(1);
  source->SetDirection(0, 1, 0);
  source->SetAngle(15);
  source->Update();
  double* bounds = source->GetOutput()->GetBounds();
  std::array<double, 2> scalarRange{bounds[2], bounds[3]};
  vtkNew<vtkElevationFilter> elevationFilter;
  elevationFilter->SetScalarRange(scalarRange.data());
  elevationFilter->SetLowPoint(0, bounds[2], 0);
  elevationFilter->SetHighPoint(0, bounds[3], 0);
  elevationFilter->SetInputConnection(source->GetOutputPort());

  std::map<std::string, vtkNew<vtkDiscretizableColorTransferFunction>> ctf;
  ctf[names[0]] = GetRainbowCTF(modern, discretize);
  ctf[names[1]] = RescaleCTF(ctf[names[0]], scalarRange[0], scalarRange[1]);
  ctf[names[2]] = GetRainbowCTF(modern, discretize, true);
  ctf[names[3]] = RescaleCTF(ctf[names[2]], scalarRange[0], scalarRange[1]);

  auto replaceInStr = [](std::string& source, const std::string& search,
                         const std::string& replace) {
    size_t pos = 0;
    while ((pos = source.find(search, pos)) != std::string::npos)
    {
      source.replace(pos, search.length(), replace);
      pos += replace.length(); // Move past the replaced section.
    }
  };

  std::vector<vtkSmartPointer<vtkTextWidget>> textWidgets;
  std::vector<vtkSmartPointer<vtkScalarBarWidget>> sbWidgets;
  std::vector<vtkSmartPointer<vtkRenderer>> renderers;

  // Define viewport boundaries.
  std::map<std::string, std::array<double, 4>> viewports{
      {names[0], {0.0, 0.5, 0.5, 1.0}},
      {names[1], {0.0, 0.0, 0.5, 0.5}},
      {names[2], {0.5, 0.5, 1.0, 1.0}},
      {names[3], {0.5, 0.0, 1.0, 0.5}},
  };
  for (const auto& name : names)
  {

    vtkNew<vtkPolyDataMapper> mapper;
    mapper->SetInputConnection(elevationFilter->GetOutputPort());
    mapper->SetLookupTable(ctf[name]);
    mapper->SetColorModeToMapScalars();
    mapper->InterpolateScalarsBeforeMappingOn();

    vtkNew<vtkActor> actor;
    actor->SetMapper(mapper);

    vtkNew<vtkRenderer> ren;
    ren->SetViewport(viewports[name].data());
    ren->SetBackground(colors->GetColor3d("ParaViewBlueGrayBkg").GetData());
    ren->AddActor(actor);

    // Add a title.
    std::string tName = name;
    replaceInStr(tName, " ", "\n");
    vtkNew<vtkTextActor> textActor;
    textActor->SetInput(tName.c_str());
    textActor->SetTextScaleModeToViewport();
    textActor->SetTextProperty(textProperty);

    vtkNew<vtkTextRepresentation> textRep;
    textRep->EnforceNormalizedViewportBoundsOn();
    textRep->SetPosition(txtPos[name]["p"][0], txtPos[name]["p"][1]);
    textRep->SetPosition2(txtPos[name]["p2"][0], txtPos[name]["p2"][1]);

    // Create the text widget, setting the default renderer and interactor.
    vtkNew<vtkTextWidget> textWidget;
    textWidget->SetRepresentation(textRep);
    textWidget->SetTextActor(textActor);
    textWidget->SelectableOff();
    textWidget->ResizableOn();
    textWidget->SetDefaultRenderer(ren);
    textWidget->SetInteractor(iRen);
    textWidgets.push_back(textWidget);

    if (name == names[0])
    {
      vtkNew<vtkTextActor> ctfTextActor;
      ctfTextActor->SetInput(ctfName.c_str());
      ctfTextActor->SetTextScaleModeToViewport();
      ctfTextActor->SetTextProperty(titleTextProperty);

      vtkNew<vtkTextRepresentation> ctfTextRep;
      ctfTextRep->EnforceNormalizedViewportBoundsOn();
      ctfTextRep->SetPosition(ctfNamePos[ctfName]["p"][0],
                              ctfNamePos[ctfName]["p"][1]);
      ctfTextRep->SetPosition2(ctfNamePos[ctfName]["p2"][0],
                               ctfNamePos[ctfName]["p2"][1]);

      // Create the text widget, setting the default renderer and interactor.
      vtkNew<vtkTextWidget> ctfTextWidget;
      ctfTextWidget->SetRepresentation(ctfTextRep);
      ctfTextWidget->SetTextActor(ctfTextActor);
      ctfTextWidget->SelectableOff();
      ctfTextWidget->ResizableOn();
      ctfTextWidget->SetDefaultRenderer(ren);
      ctfTextWidget->SetInteractor(iRen);
      textWidgets.push_back(ctfTextWidget);
    }

    // Create the scalar bar, setting the default renderer and interactor.
    size_t pos = name.find(' ');
    if (pos == std::string::npos)
    {
      tName += '\n';
    }
    auto sbProperties = ScalarBarProperties();
    sbProperties.titleText = tName + '\n';
    sbProperties.lut = ctf[name];
    sbWidgets.push_back(MakeScalarBarWidget(sbProperties, textProperty,
                                            labelTextProperty, ren, iRen));
    renderers.push_back(ren);
  }

  for (const auto& ren : renderers)
  {
    renWin->AddRenderer(ren);
  }

  renWin->Render();

  for (size_t idx = 0; idx < renderers.size(); ++idx)
  {
    if (idx % 2 == 0)
    {
      auto camera = renderers[idx]->GetActiveCamera();
      camera->SetPosition(0, 0, 2.69986);
      camera->SetFocalPoint(0, 0, 1.16016e-17);
      camera->SetViewUp(0, 1, 0);
      camera->SetDistance(2.69986);
      camera->SetClippingRange(2.5289, 2.92158);
    }
    else
    {
      auto camera = renderers[idx]->GetActiveCamera();
      camera->SetPosition(0, 0, 2.68382);
      camera->SetFocalPoint(0, 0, 1.16016e-17);
      camera->SetViewUp(0, 1, 0);
      camera->SetDistance(2.68382);
      camera->SetClippingRange(2.51388, 2.90422);
    }
  }

  for (const auto& widget : textWidgets)
  {
    widget->On();
  }
  for (const auto& widget : sbWidgets)
  {
    widget->On();
  }

  renWin->Render();
  iRen->Start();

  return EXIT_SUCCESS;
}

namespace {
vtkNew<vtkDiscretizableColorTransferFunction>
GetRainbowCTF(bool const& modern, bool const& discretize, bool const& reverse)
{

  std::map<std::size_t, double> indices{
      {0, -1.0},      {1, -2.0 / 3.0}, {2, -1.0 / 3.0}, {3, 0},
      {4, 1.0 / 3.0}, {5, 2.0 / 3.0},  {6, 1.0}};

  //  Red, Orange #ff8000, Yellow, Green #00ff00, Cyan, Blue, Violet #8000ff
  std::map<std::size_t, std::array<double, 3>> modernRainbow{
      {0, {1.0, 0.0, 0.0}},           {1, {1.0, 128.0 / 255.0, 0.0}},
      {2, {1.0, 1.0, 0.0}},           {3, {0.0, 1.0, 0.0}},
      {4, {0.0, 1.0, 1.0}},           {5, {0.0, 0.0, 1.0}},
      {6, {128.0 / 255.0, 0.0, 1.0}},
  };
  // Red, Orange #00a500, Yellow, Green #008000, Blue #0099ff, Indigo #4400ff,
  // Violet #9900ff The mnemonic here is: "Rip out your guts before I vomit."
  std::map<std::size_t, std::array<double, 3>> newtonsRainbow{
      {0, {1.0, 0.0, 0.0}},           {1, {1.0, 165.0 / 255.0, 0.0}},
      {2, {1.0, 1.0, 0.0}},           {3, {0.0, 125.0 / 255.0, 0.0}},
      {4, {0.0, 153.0 / 255.0, 1.0}}, {5, {68.0 / 255.0, 0, 153.0 / 255.0}},
      {6, {153.0 / 255.0, 0.0, 1.0}},
  };

  vtkNew<vtkDiscretizableColorTransferFunction> ctf;
  ctf->SetColorSpaceToLab();
  ctf->SetScaleToLinear();
  ctf->SetNumberOfValues(indices.size());
  ctf->SetNanColor(0.0, 1.0, 0.0);
  ctf->SetDiscretize(discretize);

  if (modern)
  {
    if (reverse)
    {
      std::vector<size_t> revIdx;
      revIdx.reserve(modernRainbow.size());
      for (const auto& kv : modernRainbow)
      {
        revIdx.push_back(kv.first);
      }
      std::vector<std::size_t> idxRev(revIdx.rbegin(), revIdx.rend());
      size_t index{0};
      for (auto const k : idxRev)
      {
        auto v = modernRainbow[index];
        ctf->AddRGBPoint(indices[k], v[0], v[1], v[2]);
        index++;
      }
    }
    else
    {
      for (const auto& [key, value] : indices)
      {
        auto v = modernRainbow[key];
        ctf->AddRGBPoint(value, v[0], v[1], v[2]);
      }
    }
  }
  else
  {
    if (reverse)
    {
      std::vector<size_t> revIdx;
      revIdx.reserve(modernRainbow.size());
      for (const auto& kv : modernRainbow)
      {
        revIdx.push_back(kv.first);
      }
      std::vector<std::size_t> idxRev(revIdx.rbegin(), revIdx.rend());
      size_t index{0};
      for (auto const k : idxRev)
      {
        auto v = newtonsRainbow[index];
        ctf->AddRGBPoint(indices[k], v[0], v[1], v[2]);
        index++;
      }
    }
    else
    {
      for (const auto& [key, value] : indices)
      {
        auto v = newtonsRainbow[key];
        ctf->AddRGBPoint(value, v[0], v[1], v[2]);
      }
    }
  }

  return ctf;
}

std::vector<double> Rescale(std::vector<double> const& values,
                            double const& newMin, double const& newMax)
{
  std::vector<double> res;
  double oldMin = *std::min_element(values.begin(), values.end());
  double oldMax = *std::max_element(values.begin(), values.end());
  for (size_t i = 0; i < values.size(); ++i)
  {
    double newV =
        (newMax - newMin) / (oldMax - oldMin) * (values[i] - oldMin) + newMin;
    // double newV1 =
    //     (newMax - newMin) / (oldMax - oldMin) * (values[i] - oldMax) +
    //     newMax;
    res.push_back(newV);
  }
  return res;
}

vtkNew<vtkDiscretizableColorTransferFunction>
RescaleCTF(vtkNew<vtkDiscretizableColorTransferFunction> const& oldCTF,
           double const& newMin, double const& newMax)
{
  double r0;
  double r1;

  if (newMin > newMax)
  {
    r0 = newMax;
    r1 = newMin;
  }
  else
  {
    r0 = newMin;
    r1 = newMax;
  }

  std::vector<double> xv;
  std::vector<std::array<double, 3>> rgbv;
  double nv[6] = {0, 0, 0, 0, 0, 0};
  for (auto i = 0; i < oldCTF->GetNumberOfValues(); ++i)
  {
    oldCTF->GetNodeValue(i, nv);
    double x = nv[0];
    std::array<double, 3> rgb;
    for (auto j = 1; j < 4; ++j)
    {
      rgb[j - 1] = nv[j];
    }
    xv.push_back(x);
    rgbv.push_back(rgb);
  }
  std::vector<double> xvr = Rescale(xv, r0, r1);

  vtkNew<vtkDiscretizableColorTransferFunction> newCTF;
  newCTF->SetColorSpace(oldCTF->GetColorSpace());
  newCTF->SetScale(oldCTF->GetScale());
  newCTF->SetNanColor(oldCTF->GetNanColor());
  newCTF->SetNumberOfValues(xvr.size());
  newCTF->SetDiscretize(oldCTF->GetDiscretize());

  newCTF->SetBelowRangeColor(oldCTF->GetBelowRangeColor());
  newCTF->SetUseBelowRangeColor(oldCTF->GetUseBelowRangeColor());
  newCTF->SetAboveRangeColor(oldCTF->GetAboveRangeColor());
  newCTF->SetUseAboveRangeColor(oldCTF->GetUseAboveRangeColor());

  for (size_t i = 0; i < xvr.size(); ++i)
  {
    newCTF->AddRGBPoint(xvr[i], rgbv[i][0], rgbv[i][1], rgbv[i][2]);
  }

  return newCTF;
}

vtkNew<vtkScalarBarWidget>
MakeScalarBarWidget(ScalarBarProperties& sbProperties,
                    vtkTextProperty* textProperty,
                    vtkTextProperty* labelTextProperty, vtkRenderer* ren,
                    vtkRenderWindowInteractor* iren)
{
  vtkNew<vtkScalarBarActor> sbActor;
  sbActor->SetLookupTable(sbProperties.lut);
  sbActor->SetTitle(sbProperties.titleText.c_str());
  sbActor->UnconstrainedFontSizeOn();
  sbActor->SetNumberOfLabels(sbProperties.number_of_labels);
  sbActor->SetTitleTextProperty(textProperty);
  sbActor->SetLabelTextProperty(labelTextProperty);
  sbActor->SetLabelFormat(sbProperties.labelFormat.c_str());

  vtkNew<vtkScalarBarRepresentation> sbRep;
  sbRep->EnforceNormalizedViewportBoundsOn();
  sbRep->SetOrientation(sbProperties.orientation);
  // Set the position.
  sbRep->GetPositionCoordinate()->SetCoordinateSystemToNormalizedViewport();
  sbRep->GetPosition2Coordinate()->SetCoordinateSystemToNormalizedViewport();
  if (sbProperties.orientation)
  {
    auto p1 = sbProperties.positionV["p"];
    auto p2 = sbProperties.positionV["p2"];
    sbRep->GetPositionCoordinate()->SetValue(p1.data());
    sbRep->GetPosition2Coordinate()->SetValue(p2.data());
  }
  else
  {
    auto p1 = sbProperties.positionH["p"];
    auto p2 = sbProperties.positionH["p2"];
    sbRep->GetPositionCoordinate()->SetValue(p1.data());
    sbRep->GetPosition2Coordinate()->SetValue(p2.data());
  }

  vtkNew<vtkScalarBarWidget> widget;
  widget->SetRepresentation(sbRep);
  widget->SetScalarBarActor(sbActor);
  widget->SetDefaultRenderer(ren);
  widget->SetInteractor(iren);
  widget->EnabledOn();

  return widget;
}

TTextPositions GetTextPositions(std::vector<std::string> const& names,
                                int const justification,
                                int const vertical_justification,
                                double const width, double const height)
{
  // The gap between the left or right edge of the screen and the text.
  auto dx = 0.02;
  auto w = abs(width);
  if (w > 0.96)
  {
    w = 0.96;
  }

  auto y0 = 0.01;
  auto h = abs(height);
  if (h > 0.9)
  {
    h = 0.9;
  }
  auto dy = h;
  if (vertical_justification == VTK_TEXT_TOP)
  {
    y0 = 1.0 - (dy + y0);
  }
  if (vertical_justification == VTK_TEXT_CENTERED)
  {
    y0 = 0.5 - (dy / 2.0 + y0);
  }

  auto minmaxIt =
      std::minmax_element(names.begin(), names.end(),
                          [](const std::string& a, const std::string& b) {
                            return a.length() < b.length();
                          });

  // auto nameLenMin = minmaxIt.first->size();
  auto nameLenMax = minmaxIt.second->size();

  TTextPositions textPositions;
  for (const auto& k : names)
  {
    auto sz = k.size();
    auto delta_sz = w * sz / nameLenMax;
    if (delta_sz > w)
    {
      delta_sz = w;
    }

    double x0 = 0;
    if (justification == VTK_TEXT_CENTERED)
    {
      x0 = 0.5 - delta_sz / 2.0;
    }
    else if (justification == VTK_TEXT_RIGHT)
    {
      x0 = 1.0 - dx - delta_sz;
    }
    else
    {
      // Default is left justification.
      x0 = dx;
    }
    textPositions[k] = {{"p", {x0, y0}}, {"p2", {delta_sz, dy}}};
    // For testing.
    // std::cout << k << std::endl;
    // std::cout << "  p: " << textPositions[k]["p"][0] << ", "
    //           << textPositions[k]["p"][1] << std::endl;
    // std::cout << " p2: " << textPositions[k]["p2"][0] << ", "
    //           << textPositions[k]["p2"][1] << std::endl;
  }
  return textPositions;
}

std::string Title(const std::string& input)
{
  std::string result = input;
  bool capitalizeNext = true;

  for (char& c : result)
  {
    if (std::isspace(static_cast<unsigned char>(c)))
    {
      capitalizeNext = true;
    }
    else if (capitalizeNext)
    {
      c = static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
      capitalizeNext = false;
    }
    else
    {
      c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
    }
  }
  return result;
}

} // namespace

CMakeLists.txt

cmake_minimum_required(VERSION 3.12 FATAL_ERROR)

project(RescaleReverseLUT)

find_package(VTK COMPONENTS 
  CommonColor
  CommonCore
  FiltersCore
  FiltersSources
  InteractionStyle
  InteractionWidgets
  RenderingAnnotation
  RenderingContextOpenGL2
  RenderingCore
  RenderingFreeType
  RenderingGL2PSOpenGL2
  RenderingOpenGL2
  cli11
  fmt
)

if (NOT VTK_FOUND)
  message(FATAL_ERROR "RescaleReverseLUT: 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(RescaleReverseLUT MACOSX_BUNDLE RescaleReverseLUT.cxx )
  target_link_libraries(RescaleReverseLUT PRIVATE ${VTK_LIBRARIES}
)
# vtk_module_autoinit is needed
vtk_module_autoinit(
  TARGETS RescaleReverseLUT
  MODULES ${VTK_LIBRARIES}
)

Download and Build RescaleReverseLUT

Click here to download RescaleReverseLUT and its CMakeLists.txt file. Once the tarball RescaleReverseLUT.tar has been downloaded and extracted,

cd RescaleReverseLUT/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:

./RescaleReverseLUT

WINDOWS USERS

Be sure to add the VTK bin directory to your path. This will resolve the VTK dll's at run time.