Skip to content

ExternalContour

Repository source: ExternalContour

Description

Compute the external contour of a polydata.

At first, it creates a black and white image of a scene containing the polydata and then extracts the contour of the black shape from the image.

Note

A longstanding bug is fixed, see the discussion here.

Other languages

See (Cxx)

Question

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

Code

ExternalContour.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.vtkCommonTransforms import vtkTransform
from vtkmodules.vtkFiltersCore import (
    vtkCleanPolyData,
    vtkContourFilter
)
from vtkmodules.vtkFiltersGeneral import (
    vtkTransformPolyDataFilter
)
from vtkmodules.vtkFiltersSources import vtkSphereSource
# noinspection PyUnresolvedReferences
from vtkmodules.vtkIOImage import vtkPNGWriter
from vtkmodules.vtkIOXML import vtkXMLPolyDataReader
from vtkmodules.vtkInteractionStyle import vtkInteractorStyleTrackballCamera
from vtkmodules.vtkRenderingCore import (
    vtkActor,
    vtkCamera,
    vtkPolyDataMapper,
    vtkRenderWindow,
    vtkRenderWindowInteractor,
    vtkRenderer,
    vtkWindowToImageFilter
)


def get_program_parameters():
    import argparse
    description = 'ExtractSelection'
    epilogue = """
   """
    parser = argparse.ArgumentParser(description=description, epilog=epilogue,
                                     formatter_class=argparse.RawDescriptionHelpFormatter)
    parser.add_argument('-f', '--file_name', default=None, help='Bunny.vtp')
    args = parser.parse_args()
    return args.file_name


def main():
    colors = vtkNamedColors()

    fn = get_program_parameters()

    if fn:
        fn_path = Path(fn)
        if not fn_path.is_file():
            print('Unable to find: ', fn_path)
            return
        else:
            reader = vtkXMLPolyDataReader(file_name=fn_path)
            data3d = reader.update().output
    else:
        source = vtkSphereSource(center=(0.0, 0.0, 5.0), radius=2.0, theta_resolution=20, phi_resolution=20)
        data3d = source.update().output

    bounds_data = data3d.bounds
    center_data = data3d.center

    #  Black and white scene with the data in order to print the view.
    mapper_data = vtkPolyDataMapper()
    data3d >> mapper_data
    actor_data = vtkActor(mapper=mapper_data)
    actor_data.property.color = colors.GetColor3d("Black")

    tmp_rend = vtkRenderer(background=colors.GetColor3d("White"))

    tmp_rend.AddActor(actor_data)
    tmp_rend.ResetCamera()
    tmp_rend.active_camera.parallel_projection = True

    tmp_rw = vtkRenderWindow(off_screen_rendering=True)
    tmp_rw.AddRenderer(tmp_rend)

    tmp_rw.Render()

    # Get a print of the window.
    window_to_image_filter = vtkWindowToImageFilter(input=tmp_rw, scale=2)
    window_to_image_filter.update()

    # writer = vtkPNGWriter(file_name="ExternalContourWindowPrint.png")
    # window_to_image_filter >> writer
    # writer.Write()

    # Extract the silhouette corresponding to the black limit of the image.
    cont_filter = vtkContourFilter()
    cont_filter.SetValue(0, 255)

    # Clean the data.
    clean = vtkCleanPolyData()
    window_to_image_filter >> cont_filter >> clean
    contour = clean.update().output

    # Make the contour coincide with the data.

    bounds_contour = contour.bounds
    ratio_x = (bounds_data[1] - bounds_data[0]) / (bounds_contour[1] - bounds_contour[0])
    ratio_y = (bounds_data[3] - bounds_data[2]) / (bounds_contour[3] - bounds_contour[2])

    # Rescale the contour so that it shares the same bounds as the input data.
    transform1 = vtkTransform()
    transform1.Scale(ratio_x, ratio_y, 1.0)

    tfilter1 = vtkTransformPolyDataFilter(transform=transform1, input_data=contour)

    contour = tfilter1.update().output

    # Translate the contour so that it shares the same center as the input data.
    center_contour = contour.center
    trans_x = center_data[0] - center_contour[0]
    trans_y = center_data[1] - center_contour[1]
    trans_z = center_data[2] - center_contour[2]

    transform2 = vtkTransform()
    transform2.Translate(trans_x, trans_y, trans_z)

    tfilter2 = vtkTransformPolyDataFilter(transform=transform2, input_data=contour)

    contour = tfilter2.update().output

    # Updating the color of the data.
    actor_data.property.color = colors.GetColor3d("MistyRose")

    # Create a mapper and actor of the silhouette.
    mapper_contour = vtkPolyDataMapper(input_data=contour)

    actor_contour = vtkActor(mapper=mapper_contour)
    actor_contour.property.line_width = 2.0

    # Create the renderers, render window, and interactor.

    renderer1 = vtkRenderer(viewport=(0.0, 0.0, 0.5, 1.0), background=colors.GetColor3d("DarkSlateGray"))
    renderer2 = vtkRenderer(viewport=(0.5, 0.0, 1.0, 1.0), background=colors.GetColor3d("MidnightBlue"))

    render_window = vtkRenderWindow(size=(400, 400), window_name="ExternalContour")
    render_window.AddRenderer(renderer1)
    render_window.AddRenderer(renderer2)

    style = vtkInteractorStyleTrackballCamera()

    render_window_interactor = vtkRenderWindowInteractor()
    render_window_interactor.render_window = render_window
    render_window_interactor.interactor_style = style

    # Add the actors.
    renderer1.AddActor(actor_data)
    # renderer1.AddActor(actor_contour)
    renderer2.AddActor(actor_contour)

    # Set the same initial view as in renderer1.
    render_window.Render()
    ren2_camera = vtkCamera()
    ren2_camera.DeepCopy(renderer1.active_camera)
    renderer2.active_camera = ren2_camera
    # If you want the views linked.
    # renderer2.active_camera = renderer1.active_camera

    render_window.Render()
    render_window_interactor.Start()


if __name__ == '__main__':
    main()