Cannot Control Opacity of vtkImageActors

Hello,

I am displaying one image overlaying another image in a vtkRenderWindow. I do so by using vtkImageActor. Prior to adding each vtkImageActor to the vtkRenderer, each image has been mapped through vtkImageMapToColors, which has been assigned a vtkLookupTable.

The images display with some transparency, and I cannot seem to change this behavior. The vtkImageActor.SetOpacity() function seems to have no affect at all.

I have read some discussions about this on this and other forums, but I cannot understand the proposed solutions.

Can someone help?

Thank you,
Michelle

Code snippet. dicom1 and dicom2 are DICOMs read with vtkDICOMImageReader, then piped through various filters, include vtkImageReslice.

# create a grayscale lookup table for background image
bw_table = vtk.vtkLookupTable()
bw_table.SetRange(0, 1000)  # image intensity range
bw_table.SetValueRange(0.0, 1.0)  # from black to white
bw_table.SetSaturationRange(0.0, 0.0)  # no color saturation
bw_table.SetRampToLinear()
bw_table.Build()

# create a color lookup table for foreground image
jet_table = vtk.vtkLookupTable()
jet_table.SetRange(-5, 20)
jet_table.SetBelowRangeColor(0, 0, 1.0, 1.0)  # RGBA
jet_table.UseBelowRangeColorOn()
jet_table.SetAboveRangeColor(1.0, 0, 0, 1.0)  # RGBA
jet_table.UseAboveRangeColorOn()
jet_table.SetRampToLinear()
jet_table.SetAlphaRange(1.0, 1.0)
jet_table.SetValueRange(1.0, 1.0)
jet_table.SetSaturationRange(1.0, 1.0)
jet_table.SetHueRange(0.667, 0)  # 0.667 == blue, 0 == red
jet_table.Build()

# mappers
bw_mapper = vtk.vtkImageMapToColors()
bw_mapper.SetLookupTable(bw_table)
bw_mapper.SetInputConnection(dicom1.GetOutputPort())
color_mapper = vtk.vtkImageMapToColors()
color_mapper.SetLookupTable(jet_table)
color_mapper.SetInputConnection(dicom2.GetOutputPort())

# display window
window = vtk.vtkRenderWindow()

# set up interaction
...

# actors
bw_actor = vtk.vtkImageActor()
bw_actor.SetOpacity(1.0)   # <=== doesn't seem to have any affect
bw_actor.GetMapper().SetInputConnection(bw_mapper.GetOutputPort())
bw_actor.SetScale(1.0, 1.0, 1.0)
bw_actor.SetPosition(0.0, 0.0, 0.0)
bw_actor.SetPickable(0)
bw_actor.Update()
color_actor = vtk.vtkImageActor()
color_actor.SetOpacity(0.25)  # <=== doesn't seem to have any affect
color_actor.GetMapper().SetInputConnection(color_mapper.GetOutputPort())
color_actor.SetPickable(1)
color_actor.Update()

# renderer
renderer = vtk.vtkRenderer()
renderer.AddActor(bw_actor)
renderer.AddActor(color_actor)

# render
window.AddRenderer(temp_renderer)
window.Render()

# start interaction
...

The best way to perform image compositing is to put the images into a vtkImageStack, and then add the vtkImageStack to the renderer (instead of adding the images directly to the renderer).

Another method is to call actor.GetProperty().BackingOff() on all but the bottom-most actor. This will not be as robust as using vtkImageStack because it relies on depth-buffer compositing. The depth buffer has limited precision, and sometimes the GPU incorrectly orders the fragments.

Thanks, David. This is the second time you’ve suggested I use vtkImageStack() on this project, so I’d better read up on it and give it a try!

  • Michelle

David,

The output of vtkImageReslice.GetOutput() is a vtkImageData object. How can I add this to a vtkImageStack? From the documentation, it looks like vtkImageData.AddImage() requires a vktImageSlice object.

Thanks,
Michelle

Add the vtkImageActor objects to the stack, not the vtkImageData objects. The stack collects the actors into a group to ensure they’re properly composited when rendered.

Once the actors are in the stack, do not add them to the renderer. Just add the stack to the renderer.

Here is what the actors look like when individually added to the renderer. It is a resliced volume overlaid on an MRI. (The resliced volume has unwanted blue background, and unwanted transparency)

renderer - vtk.vtkRenderer()
renderer.AddActor(bw_actor)
renderer.AddActor(color_actor)
renderer.AddActor(color_bar_actor)

Here is what the stack looks like. Hmm. I must be missing some steps! :slight_smile:

stack = vtk.vtkImageStack()
stack.AddImage(bw_actor)
stack.AddImage(color_actor)
renderer = vtk.vtkRenderer()
renderer.AddViewProp(stack)

Is there an obvious mistake I am making?

  • Michelle

In your new code, did you forget to set the ColorWindow and ColorLevel? Also the 2nd image doesn’t have a vtkScalarBarActor, which makes me suspicious that the addition of vtkImageStack wasn’t the only change to the code. I’d need to see more of the code to be able to know what is going wrong.

Sorry about that. I thought it would be helpful to only grab the key lines of code. But you’re right, there could easily be something that I am missing in the rest of the code. I hope this isn’t too much information, but here is the portion of my code that maps and renders the two images (as well as a color_bar). current_dynamic, current_delta_temp, and anatomy are all vtkImageReslice objects.

# Create a grey scale lookup table for the anatomy
bw_table = vtk.vtkLookupTable()
bw_table.SetRange(0, 1000)  # image intensity range
bw_table.SetValueRange(0.0, 1.0)  # from black to white
bw_table.SetSaturationRange(0.0, 0.0)  # no color saturation
bw_table.SetRampToLinear()
bw_table.Build()

# Create a color lookup table for the thermometry series
jet_table = vtk.vtkLookupTable()
if displaying == 1 or displaying == 2 or displaying == 5:
    jet_table.SetRange(-M_PI, M_PI)
else:
    jet_table.SetRange(-5, 20)
jet_table.SetBelowRangeColor(0, 0, 1.0, 1.0)  # RGB A
jet_table.UseBelowRangeColorOn()
jet_table.SetAboveRangeColor(1.0, 0, 0, 1.0)  # RGB A
jet_table.UseAboveRangeColorOn()
jet_table.SetRampToLinear()
jet_table.SetAlphaRange(1.0, 1.0)
jet_table.SetValueRange(1.0, 1.0)
jet_table.SetSaturationRange(1.0, 1.0)
jet_table.SetHueRange(0.667, 0)  # 0.667 == blue, 0 == red
jet_table.Build()

# Map the images through the lookup tables
anatomy_mapper = vtk.vtkImageMapToColors()
anatomy_mapper.SetLookupTable(bw_table)
anatomy_mapper.SetInputConnection(anatomy.GetOutputPort())
delta_temp_mapper = vtk.vtkImageMapToColors()
delta_temp_mapper.SetLookupTable(jet_table)
if displaying == 1 or displaying == 5:  # displaying original phase data
    delta_temp_mapper.SetInputConnection(current_dynamic.GetOutputPort())
else:  # displaying diff or delta data
    delta_temp_mapper.SetInputConnection(current_delta_temp.GetOutputPort())

# Display window
window = vtk.vtkRenderWindow()
window.SetSize(render_window_width, 750)

# Set up the interaction
interactorStyle = vtk.vtkInteractorStyleImage()
interactor = vtk.vtkRenderWindowInteractor()
interactor.SetInteractorStyle(interactorStyle)
window.SetInteractor(interactor)

# Set up the actors
#   anatomy
bw_actor = vtk.vtkImageActor()
bw_actor.SetOpacity(1.0)  # doesn't seem to work
bw_actor.GetMapper().SetInputConnection(anatomy_mapper.GetOutputPort())
bw_actor.SetScale(1.0, 1.0, 1.0)
bw_actor.SetPosition(0.0, 0.0, 0.0)
bw_actor.SetPickable(0)
bw_actor.Update()
#   thermometry
jet_actor = vtk.vtkImageActor()
jet_actor.SetOpacity(1.0)
jet_actor.GetMapper().SetInputConnection(delta_temp_mapper.GetOutputPort())
jet_actor.SetPickable(1)
jet_actor.Update()
#   color scale bar
scale_bar_actor = vtk.vtkScalarBarActor()
scale_bar_actor.SetLookupTable(delta_temp_mapper.GetLookupTable())
scale_bar_actor.SetLookupTable(jet_table)
scale_bar_actor.SetWidth(0.1)  # viewport coordinates
scale_bar_actor.SetHeight(0.515)
scale_bar_actor.UnconstrainedFontSizeOn()
scale_bar_actor.GetLabelTextProperty().BoldOff()
scale_bar_actor.GetLabelTextProperty().SetItalic(0)
scale_bar_actor.GetLabelTextProperty().SetFontSize(14)
scale_bar_actor.SetNumberOfLabels(6)
scale_bar_actor.SetLabelFormat("%0.1f")
scale_bar_actor.SetPosition(0.9, 0.239)

# image stack? 
stack = vtk.vtkImageStack() 
stack.AddImage(bw_actor)
stack.AddImage(jet_actor)

# renderers
#   left viewport
temp_renderer = vtk.vtkRenderer()

#  ORIGINAL =============================
# if displaying == 4 or displaying == 5:
#     temp_renderer.AddActor(bw_actor)
# temp_renderer.AddActor(jet_actor)
# temp_renderer.AddActor2D(scale_bar_actor)
# NEW ==================================
temp_renderer.AddViewProp(stack)

window.AddRenderer(temp_renderer)
temp_renderer.SetViewport(0, 0, 0.5, 1.0)

# for displaying coordinates and voxel value - temperature
#   data picker
picker = vtk.vtkVolumePicker()
#   annotation
anno = vtk.vtkCornerAnnotation()
anno.SetLinearFontScaleFactor(2)
anno.SetNonlinearFontScaleFactor(1)
anno.SetMaximumFontSize(18)
anno.GetTextProperty().SetColor(1, 1, 1)
anno.SetPickable(0)
anno.SetText(3, "Dynamic: " + str(curr_dynamic_num))
temp_renderer.AddViewProp(anno)

window.Render()
window.SetWindowName(thermometryDICOM)  # Note - needs to be set AFTER window is rendered

I tried the code, and it looks like vtkImageActor is rescaling the color values before displaying them. This only happens when vtkImageActor is used with vtkImageStack. This seems very strange, and I’ll have to investigate to see why this happens.

Luckily, there is an easy fix that you can apply in your code: use vtkImageSlice instead of vtkImageActor. The vtkImageSlice class is actually the superclass of vtkImageActor and it shares a lot of the same functionality but it is used a little differently, e.g. you have to explicitly tell it what mapper to use (just like vtkActor needs a vtkDataSetMapper).

bw_actor = vtk.vtkImageSlice()
bw_actor.SetMapper(vtk.vtkImageSliceMapper())
bw_actor.GetProperty().SetOpacity(1.0)
bw_actor.GetMapper().SetInputConnection(anatomy_mapper.GetOutputPort())

It works with vtkImageStack and the SetOpacity() method works, too (but SetOpacity() is only used for the overlay image, not the underlay).

Here is the whole code that I used to test:

import vtk
import math

M_PI = math.pi

# miscellaneous setings
curr_dynamic_num = 0
displaying = 5
render_window_width = 1024
thermometryDICOM = "TEMP"

anatomy = vtk.vtkImageGaussianSource()
anatomy.SetWholeExtent(0, 1023, 0, 1023, 0, 0)
anatomy.SetCenter(500, 500, 0)
anatomy.SetMaximum(1000)
anatomy.SetStandardDeviation(100)

current_dynamic = vtk.vtkImageGaussianSource()
current_dynamic.SetWholeExtent(0, 1023, 0, 1023, 0, 0)
current_dynamic.SetCenter(500, 500, 0)
current_dynamic.SetMaximum(1000)
current_dynamic.SetStandardDeviation(100)

# Create a grey scale lookup table for the anatomy
bw_table = vtk.vtkLookupTable()
bw_table.SetRange(0, 1000)  # image intensity range
bw_table.SetValueRange(0.0, 1.0)  # from black to white
bw_table.SetSaturationRange(0.0, 0.0)  # no color saturation
bw_table.SetRampToLinear()
bw_table.Build()

# Create a color lookup table for the thermometry series
jet_table = vtk.vtkLookupTable()
if displaying == 1 or displaying == 2 or displaying == 5:
    jet_table.SetRange(-M_PI, M_PI)
else:
    jet_table.SetRange(-5, 20)
jet_table.SetBelowRangeColor(0, 0, 1.0, 1.0)  # RGB A
jet_table.UseBelowRangeColorOn()
jet_table.SetAboveRangeColor(1.0, 0, 0, 1.0)  # RGB A
jet_table.UseAboveRangeColorOn()
jet_table.SetRampToLinear()
jet_table.SetAlphaRange(1.0, 1.0)
jet_table.SetValueRange(1.0, 1.0)
jet_table.SetSaturationRange(1.0, 1.0)
jet_table.SetHueRange(0.667, 0)  # 0.667 == blue, 0 == red
jet_table.Build()

# Map the images through the lookup tables
anatomy_mapper = vtk.vtkImageMapToColors()
anatomy_mapper.SetLookupTable(bw_table)
anatomy_mapper.SetInputConnection(anatomy.GetOutputPort())
delta_temp_mapper = vtk.vtkImageMapToColors()
delta_temp_mapper.SetLookupTable(jet_table)
if displaying == 1 or displaying == 5:  # displaying original phase data
    delta_temp_mapper.SetInputConnection(current_dynamic.GetOutputPort())
else:  # displaying diff or delta data
    delta_temp_mapper.SetInputConnection(current_delta_temp.GetOutputPort())

# Display window
window = vtk.vtkRenderWindow()
window.SetSize(render_window_width, 750)

# Set up the interaction
interactorStyle = vtk.vtkInteractorStyleImage()
interactor = vtk.vtkRenderWindowInteractor()
interactor.SetInteractorStyle(interactorStyle)
window.SetInteractor(interactor)

# Set up the actors
#   anatomy
bw_actor = vtk.vtkImageSlice()
bw_actor.SetMapper(vtk.vtkImageSliceMapper())
bw_actor.GetProperty().SetOpacity(1.0)  # doesn't seem to work
bw_actor.GetMapper().SetInputConnection(anatomy_mapper.GetOutputPort())
bw_actor.SetScale(1.0, 1.0, 1.0)
bw_actor.SetPosition(0.0, 0.0, 0.0)
bw_actor.SetPickable(0)
bw_actor.Update()
#   thermometry
jet_actor = vtk.vtkImageSlice()
jet_actor.SetMapper(vtk.vtkImageSliceMapper())
jet_actor.GetProperty().SetOpacity(1.0)
jet_actor.GetMapper().SetInputConnection(delta_temp_mapper.GetOutputPort())
jet_actor.SetPickable(1)
jet_actor.Update()
#   color scale bar
scale_bar_actor = vtk.vtkScalarBarActor()
scale_bar_actor.SetLookupTable(delta_temp_mapper.GetLookupTable())
scale_bar_actor.SetLookupTable(jet_table)
scale_bar_actor.SetWidth(0.1)  # viewport coordinates
scale_bar_actor.SetHeight(0.515)
scale_bar_actor.UnconstrainedFontSizeOn()
scale_bar_actor.GetLabelTextProperty().BoldOff()
scale_bar_actor.GetLabelTextProperty().SetItalic(0)
scale_bar_actor.GetLabelTextProperty().SetFontSize(14)
scale_bar_actor.SetNumberOfLabels(6)
scale_bar_actor.SetLabelFormat("%0.1f")
scale_bar_actor.SetPosition(0.9, 0.239)

# image stack? 
stack = vtk.vtkImageStack() 
stack.AddImage(bw_actor)
stack.AddImage(jet_actor)

# renderers
#   left viewport
temp_renderer = vtk.vtkRenderer()

#  ORIGINAL =============================
#if displaying == 4 or displaying == 5:
#    temp_renderer.AddActor(bw_actor)
#temp_renderer.AddActor(jet_actor)
# NEW ==================================
temp_renderer.AddViewProp(stack)

temp_renderer.AddActor2D(scale_bar_actor)

window.AddRenderer(temp_renderer)
#temp_renderer.SetViewport(0, 0, 0.5, 1.0)

# for displaying coordinates and voxel value - temperature
#   data picker
picker = vtk.vtkVolumePicker()
#   annotation
anno = vtk.vtkCornerAnnotation()
anno.SetLinearFontScaleFactor(2)
anno.SetNonlinearFontScaleFactor(1)
anno.SetMaximumFontSize(18)
anno.GetTextProperty().SetColor(1, 1, 1)
anno.SetPickable(0)
anno.SetText(3, "Dynamic: " + str(curr_dynamic_num))
temp_renderer.AddViewProp(anno)

window.Render()
window.SetWindowName(thermometryDICOM)  # Note - needs to be set AFTER window is rendered
interactor.Start()
1 Like