RescaleReverseLUT
vtk-examples/Python/Utilities/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.
Of course, if you are generating the scalars, it may be easier to just change the scalar range of your filter. However, this may not be possible in some cases.
Here, we generate the original Color Transfer Function (CTF) corresponding to the seven colors that Isaac Newton labeled when dividing the spectrum of visible light in 1672. There are seven colors and the scalar range is [-1, 1].
The cylinder has a vtkElevationFilter applied to it with a scalar range of [0, 1].
There are four images:
- Original - The cylinder is colored by only the top four colors from the CTF. This is because the elevation scalar range on the cylinder is [0, 1] and the CTF scalar range is [-1, 1]. So the coloring is green->violet.
- Reversed - We have reversed the colors from the original CTF and the lower four colors in the original CTF are now the top four colors used to color the cylinder. The coloring is now green->red.
- Rescaled - The original CTF is rescaled to the range [0, 1] to match the scalar range of the elevation filter. The coloring is red->violet.
- Rescaled and Reversed - The original CTF is rescaled to the range [0, 1] and the colors reversed. The coloring is violet->red.
Other languages
See (Cxx)
Question
If you have a question about this example, please use the VTK Discourse Forum
Code¶
RescaleReverseLUT.py
#!/usr/bin/env python3
# noinspection PyUnresolvedReferences
import vtkmodules.vtkInteractionStyle
# noinspection PyUnresolvedReferences
import vtkmodules.vtkRenderingFreeType
# noinspection PyUnresolvedReferences
import vtkmodules.vtkRenderingOpenGL2
from vtkmodules.vtkCommonColor import vtkNamedColors
from vtkmodules.vtkFiltersCore import vtkElevationFilter
from vtkmodules.vtkFiltersSources import vtkCylinderSource
from vtkmodules.vtkInteractionStyle import vtkInteractorStyleTrackballCamera
from vtkmodules.vtkRenderingAnnotation import vtkScalarBarActor
from vtkmodules.vtkRenderingCore import (
vtkActor,
vtkActor2D,
vtkDiscretizableColorTransferFunction,
vtkPolyDataMapper,
vtkRenderWindow,
vtkRenderWindowInteractor,
vtkRenderer,
vtkTextMapper,
vtkTextProperty
)
def main():
colors = vtkNamedColors()
colors.SetColor('ParaViewBkg', 82, 87, 110, 255)
ren_win = vtkRenderWindow()
ren_win.SetSize(640 * 2, 480 * 2)
ren_win.SetWindowName('RescaleReverseLUT')
iren = vtkRenderWindowInteractor()
iren.SetRenderWindow(ren_win)
style = vtkInteractorStyleTrackballCamera()
iren.SetInteractorStyle(style)
ctf = list()
ctf.append(get_ctf(False))
ctf.append(rescale_ctf(ctf[0], 0, 1, False))
ctf.append(rescale_ctf(ctf[0], *ctf[0].GetRange(), True))
ctf.append(rescale_ctf(ctf[0], 0, 1, True))
# Define viewport ranges.
xmins = [0.0, 0.0, 0.5, 0.5]
xmaxs = [0.5, 0.5, 1.0, 1.0]
ymins = [0.5, 0.0, 0.5, 0.0]
ymaxs = [1.0, 0.5, 1.0, 0.5]
# Define titles.
titles = ['Original', 'Rescaled', 'Reversed', 'Rescaled and Reversed']
# Create a common text property.
text_property = vtkTextProperty()
text_property.SetFontSize(36)
text_property.SetJustificationToCentered()
text_property.SetColor(colors.GetColor3d('LightGoldenrodYellow'))
sources = list()
elevation_filters = list()
mappers = list()
actors = list()
scalar_bars = list()
renderers = list()
text_mappers = list()
text_actors = list()
for i in range(0, 4):
cylinder = vtkCylinderSource()
cylinder.SetCenter(0.0, 0.0, 0.0)
cylinder.SetResolution(6)
cylinder.Update()
bounds = cylinder.GetOutput().GetBounds()
sources.append(cylinder)
elevation_filter = vtkElevationFilter()
elevation_filter.SetScalarRange(0, 1)
elevation_filter.SetLowPoint(0, bounds[2], 0)
elevation_filter.SetHighPoint(0, bounds[3], 0)
elevation_filter.SetInputConnection(sources[i].GetOutputPort())
elevation_filters.append(elevation_filter)
mapper = vtkPolyDataMapper()
mapper.SetInputConnection(elevation_filters[i].GetOutputPort())
mapper.SetLookupTable(ctf[i])
mapper.SetColorModeToMapScalars()
mapper.InterpolateScalarsBeforeMappingOn()
mappers.append(mapper)
actor = vtkActor()
actor.SetMapper(mappers[i])
actors.append(actor)
# Add a scalar bar.
scalar_bar = vtkScalarBarActor()
scalar_bar.SetLookupTable(ctf[i])
scalar_bars.append(scalar_bar)
text_mappers.append(vtkTextMapper())
text_mappers[i].SetInput(titles[i])
text_mappers[i].SetTextProperty(text_property)
text_actors.append(vtkActor2D())
text_actors[i].SetMapper(text_mappers[i])
# Note: The position of an Actor2D is specified in display coordinates.
text_actors[i].SetPosition(300, 16)
ren = vtkRenderer()
ren.SetBackground(colors.GetColor3d('ParaViewBkg'))
ren.AddActor(actors[i])
ren.AddActor(scalar_bars[i])
ren.AddActor(text_actors[i])
ren.SetViewport(xmins[i], ymins[i], xmaxs[i], ymaxs[i])
renderers.append(ren)
ren_win.AddRenderer(renderers[i])
ren_win.Render()
iren.Start()
def get_ctf(modern=False):
"""
Generate the color transfer function.
The seven colors corresponding to the colors that Isaac Newton labelled
when dividing the spectrum of visible light in 1672 are used.
The modern variant of these colors can be selected and used instead.
See: [Rainbow](https://en.wikipedia.org/wiki/Rainbow)
:param modern: Selects either Newton's original seven colors or modern version.
:return: The color transfer function.
"""
# name: Rainbow, creator: Andrew Maclean
# interpolationspace: RGB, space: rgb
# file name:
ctf = vtkDiscretizableColorTransferFunction()
ctf.SetColorSpaceToRGB()
ctf.SetScaleToLinear()
ctf.SetNanColor(0.5, 0.5, 0.5)
ctf.SetBelowRangeColor(0.0, 0.0, 0.0)
ctf.UseBelowRangeColorOn()
ctf.SetAboveRangeColor(1.0, 1.0, 1.0)
ctf.UseAboveRangeColorOn()
if modern:
ctf.AddRGBPoint(-1.0, 1.0, 0.0, 0.0) # Red
ctf.AddRGBPoint(-2.0 / 3.0, 1.0, 128.0 / 255.0, 0.0) # Orange #ff8000
ctf.AddRGBPoint(-1.0 / 3.0, 1.0, 1.0, 0.0) # Yellow
ctf.AddRGBPoint(0.0, 0.0, 1.0, 0.0) # Green #00ff00
ctf.AddRGBPoint(1.0 / 3.0, 0.0, 1.0, 1.0) # Cyan
ctf.AddRGBPoint(2.0 / 3.0, 0.0, 0.0, 1.0) # Blue
ctf.AddRGBPoint(1.0, 128.0 / 255.0, 0.0, 1.0) # Violet #8000ff
else:
ctf.AddRGBPoint(-1.0, 1.0, 0.0, 0.0) # Red
ctf.AddRGBPoint(-2.0 / 3.0, 1.0, 165.0 / 255.0, 0.0) # Orange #00a500
ctf.AddRGBPoint(-1.0 / 3.0, 1.0, 1.0, 0.0) # Yellow
ctf.AddRGBPoint(0.0, 0.0, 125.0 / 255.0, 0.0) # Green #008000
ctf.AddRGBPoint(1.0 / 3.0, 0.0, 153.0 / 255.0, 1.0) # Blue #0099ff
ctf.AddRGBPoint(2.0 / 3.0, 68.0 / 255.0, 0, 153.0 / 255.0) # Indigo #4400ff
ctf.AddRGBPoint(1.0, 153.0 / 255.0, 0.0, 1.0) # Violet #9900ff
ctf.SetNumberOfValues(7)
ctf.DiscretizeOn()
return ctf
def generate_new_ctf(old_ctf, new_x, new_rgb, reverse=False):
"""
Generate a new color transfer function from the old one,
adding in the new x and rgb values.
:param old_ctf: The old color transfer function.
:param new_x: The new color x-values.
:param new_rgb: The color RGB values.
:param reverse: If true, reverse the colors.
:return: The new color transfer function.
"""
new_ctf = vtkDiscretizableColorTransferFunction()
new_ctf.SetScale(old_ctf.GetScale())
new_ctf.SetColorSpace(old_ctf.GetColorSpace())
new_ctf.SetNanColor(old_ctf.GetNanColor())
if not reverse:
new_ctf.SetBelowRangeColor(old_ctf.GetBelowRangeColor())
new_ctf.SetUseBelowRangeColor(old_ctf.GetUseBelowRangeColor())
new_ctf.SetAboveRangeColor(old_ctf.GetAboveRangeColor())
new_ctf.SetUseAboveRangeColor(old_ctf.GetUseAboveRangeColor())
else:
new_ctf.SetBelowRangeColor(old_ctf.GetAboveRangeColor())
new_ctf.SetUseBelowRangeColor(old_ctf.GetUseAboveRangeColor())
new_ctf.SetAboveRangeColor(old_ctf.GetBelowRangeColor())
new_ctf.SetUseAboveRangeColor(old_ctf.GetUseBelowRangeColor())
new_ctf.SetNumberOfValues(len(new_x))
new_ctf.SetDiscretize(old_ctf.GetDiscretize())
if not reverse:
for i in range(0, len(new_x)):
new_ctf.AddRGBPoint(new_x[i], *new_rgb[i])
else:
sz = len(new_x)
for i in range(0, sz):
j = sz - (i + 1)
new_ctf.AddRGBPoint(new_x[i], *new_rgb[j])
new_ctf.Build()
return new_ctf
def rescale(values, new_min=0, new_max=1):
"""
Rescale the values.
See: https://stats.stackexchange.com/questions/25894/changing-the-scale-of-a-variable-to-0-100
:param values: The values to be rescaled.
:param new_min: The new minimum value.
:param new_max: The new maximum value.
:return: The rescaled values.
"""
res = list()
old_min, old_max = min(values), max(values)
for v in values:
new_v = (new_max - new_min) / (old_max - old_min) * (v - old_min) + new_min
# new_v1 = (new_max - new_min) / (old_max - old_min) * (v - old_max) + new_max
res.append(new_v)
return res
def rescale_ctf(ctf, new_min=0, new_max=1, reverse=False):
"""
Rescale and, optionally, reverse the colors in the color transfer function.
:param ctf: The color transfer function to rescale.
:param new_min: The new minimum value.
:param new_max: The new maximum value.
:param reverse: If true, reverse the colors.
:return: The rescaled color transfer function.
"""
if new_min > new_max:
r0 = new_max
r1 = new_min
else:
r0 = new_min
r1 = new_max
xv = list()
rgbv = list()
nv = [0] * 6
for i in range(0, ctf.GetNumberOfValues()):
ctf.GetNodeValue(i, nv)
x = nv[0]
rgb = nv[1:4]
xv.append(x)
rgbv.append(rgb)
xvr = rescale(xv, r0, r1)
return generate_new_ctf(ctf, xvr, rgbv, reverse=reverse)
if __name__ == '__main__':
main()