ImageClip
Repository source: ImageClip
Description¶
This example loads an image into the left half of the window. When you move the border widget (the green square in the bottom left corner) over the image, the region that is selected is displayed in the right half of the window.
A perspective projection (-p
) can be used instead of the parallel camera projection. If this is done, the borders of the widget can appear to be outside the image bounds.
Other languages
See (Cxx)
Question
If you have a question about this example, please use the VTK Discourse Forum
Code¶
ImageClip.py
#!/usr/bin/env python3
from pathlib import Path
# noinspection PyUnresolvedReferences
import vtkmodules.vtkInteractionStyle
# noinspection PyUnresolvedReferences
import vtkmodules.vtkRenderingOpenGL2
from vtkmodules.vtkCommonColor import vtkNamedColors
from vtkmodules.vtkCommonCore import vtkMath
from vtkmodules.vtkCommonExecutionModel import vtkStreamingDemandDrivenPipeline
from vtkmodules.vtkIOImage import (
vtkImageReader2Factory,
vtkJPEGReader
)
from vtkmodules.vtkImagingCore import vtkImageClip
from vtkmodules.vtkInteractionStyle import vtkInteractorStyleImage
from vtkmodules.vtkInteractionWidgets import (
vtkBorderRepresentation,
vtkBorderWidget
)
from vtkmodules.vtkRenderingCore import (
vtkImageActor,
vtkRenderer,
vtkRenderWindow,
vtkRenderWindowInteractor
)
def get_program_parameters():
import argparse
description = 'Clip an image.'
epilogue = '''
'''
parser = argparse.ArgumentParser(description=description, epilog=epilogue,
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument('file_name', help='The image file name to use e.g. Gourds2.jpg.')
parser.add_argument('-p', '--perspective', action='store_false', help='Use perspective projection.')
args = parser.parse_args()
return args.file_name, args.perspective
def main():
fn, parallel_projection = get_program_parameters()
fp = Path(fn)
file_check = True
if not fp.is_file():
print(f'Missing image file: {fp}.')
file_check = False
if not file_check:
return
use_factory = True
# Read the image.
if use_factory:
image_reader: vtkImageReader2Factory = vtkImageReader2Factory().CreateImageReader2(str(fp))
image_reader.file_name = fp
else:
image_reader: vtkJPEGReader = vtkJPEGReader(file_name=fp)
if not image_reader.CanReadFile(fp):
print(f'Error: cannot read {fp}')
return
image_reader.update()
extent = image_reader.output.extent
# x_min, x_max, y_min, y_max, z_in, z_max
print(f'extent: ({fmt_floats(extent, w=0, d=2, pt="f")})')
colors = vtkNamedColors()
image_actor = vtkImageActor()
image_reader >> image_actor.mapper
render_window = vtkRenderWindow()
interactor = vtkRenderWindowInteractor()
style = vtkInteractorStyleImage()
interactor.interactor_style = style
rep = vtkBorderRepresentation()
rep.BuildRepresentation()
rep.border_color = colors.GetColor3d('Lime')
border_widget = vtkBorderWidget(interactor=interactor, selectable=False, representation=rep)
interactor.render_window = render_window
# Define viewport ranges in normalized coordinates.(x_min, y_min, x_max, y_max).
left_viewport = (0.0, 0.0, 0.5, 1.0)
right_viewport = (0.5, 0.0, 1.0, 1.0)
# Setup both renderers.
left_renderer = vtkRenderer(viewport=left_viewport, background=colors.GetColor3d('DarkSlateGray'))
render_window.AddRenderer(left_renderer)
right_renderer = vtkRenderer(viewport=right_viewport, background=colors.GetColor3d('DimGray'))
render_window.AddRenderer(right_renderer)
image_clip = vtkImageClip(clip_data=True)
image_reader >> image_clip
image_reader.UpdateInformation()
image_clip.SetOutputWholeExtent(
image_reader.GetOutputInformation(0).Get(vtkStreamingDemandDrivenPipeline.WHOLE_EXTENT()))
clip_actor = vtkImageActor()
image_clip >> clip_actor.mapper
left_renderer.AddActor(image_actor)
right_renderer.AddActor(clip_actor)
border_callback = BorderCallback(left_renderer, image_actor, image_clip)
border_widget.AddObserver('InteractionEvent', border_callback)
render_window.window_name = 'ImageClip'
left_renderer.active_camera.parallel_projection = parallel_projection
left_renderer.ResetCamera()
right_renderer.active_camera.parallel_projection = parallel_projection
right_renderer.ResetCamera()
render_window.Render()
border_widget.On()
interactor.Start()
class BorderCallback:
def __init__(self, renderer, actor, clip):
self.renderer = renderer
self.image_actor = actor
self.clip_filter = clip
def __call__(self, caller, ev):
border_widget = caller
# Get the world coordinates of the two corners of the box.
lower_left_coordinate = border_widget.representation.position_coordinate
lower_left = lower_left_coordinate.GetComputedWorldValue(self.renderer)
upper_right_coordinate = border_widget.representation.position2_coordinate
upper_right = upper_right_coordinate.GetComputedWorldValue(self.renderer)
# Get the bounds (x_min, x_max, y_min, y_max, z_min, z_max)
bounds = self.image_actor.bounds
inside = lower_left[0] > bounds[0] and upper_right[0] < bounds[1] and \
lower_left[1] > bounds[2] and upper_right[1] < bounds[3]
if inside:
print(f'Lower left coordinate: ({fmt_floats(lower_left, w=0, d=2, pt="f")})')
print(f'Upper right coordinate: ({fmt_floats(upper_right, w=0, d=2, pt="f")})')
self.clip_filter.SetOutputWholeExtent(
vtkMath.Round(lower_left[0]), vtkMath.Round(upper_right[0]),
vtkMath.Round(lower_left[1]), vtkMath.Round(upper_right[1]), 0, 1)
else:
print('The box is NOT inside the image.')
def fmt_floats(v, w=0, d=6, pt='g'):
"""
Pretty print a list or tuple of floats.
:param v: The list or tuple of floats.
:param w: Total width of the field.
:param d: The number of decimal places.
:param pt: The presentation type, 'f', 'g' or 'e'.
:return: A string.
"""
pt = pt.lower()
if pt not in ['f', 'g', 'e']:
pt = 'f'
return ', '.join([f'{element:{w}.{d}{pt}}' for element in v])
if __name__ == '__main__':
main()