Skip to content

PCADemo

Repository source: PCADemo

Other languages

See (Cxx)

Question

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

Code

PCADemo.py

#!/usr/bin/env python3

# noinspection PyUnresolvedReferences
import vtkmodules.vtkInteractionStyle
# noinspection PyUnresolvedReferences
import vtkmodules.vtkRenderingOpenGL2
from vtkmodules.vtkCommonColor import vtkNamedColors
from vtkmodules.vtkCommonCore import (
    reference
)
from vtkmodules.vtkCommonCore import vtkBoxMuellerRandomSequence
from vtkmodules.vtkCommonCore import (
    vtkDoubleArray,
    vtkPoints
)
from vtkmodules.vtkCommonDataModel import (
    vtkLine,
    vtkPolyData
)
from vtkmodules.vtkCommonDataModel import vtkTable
from vtkmodules.vtkCommonTransforms import vtkTransform
from vtkmodules.vtkFiltersGeneral import (
    vtkTransformPolyDataFilter,
    vtkVertexGlyphFilter
)
from vtkmodules.vtkFiltersSources import (
    vtkLineSource,
    vtkSphereSource
)
from vtkmodules.vtkFiltersStatistics import (
    vtkPCAStatistics,
    vtkStatisticsAlgorithm
)
from vtkmodules.vtkRenderingCore import (
    vtkActor,
    vtkGlyph3DMapper,
    vtkPolyDataMapper,
    vtkRenderer,
    vtkRenderWindow,
    vtkRenderWindowInteractor
)


def main():
    colors = vtkNamedColors()

    random_sequence = vtkBoxMuellerRandomSequence()

    points = vtkPoints()
    for i in range(0, 200):
        x = random_sequence.GetScaledValue(0, 2)
        random_sequence.Next()
        y = random_sequence.GetScaledValue(0, 5)
        random_sequence.Next()
        points.InsertNextPoint((x, y, 0.0))

    polydata = vtkPolyData(points=points)

    transform = vtkTransform()
    transform.RotateZ(30)

    transform_filter = vtkTransformPolyDataFilter(transform=transform, input_data=polydata)
    transform_filter.update()

    # These would be all of your 'x' values.
    x_array = vtkDoubleArray(name='x', number_of_components=1)
    # These would be all of your 'y' values.
    y_array = vtkDoubleArray(name='y', number_of_components=1)

    for i in range(0, polydata.number_of_points):
        p = transform_filter.output.GetPoint(i)
        x_array.InsertNextValue(p[0])
        y_array.InsertNextValue(p[1])

    dataset_table = vtkTable()
    dataset_table.AddColumn(x_array)
    dataset_table.AddColumn(y_array)

    pca_statistics = vtkPCAStatistics()
    pca_statistics.SetInputData(vtkStatisticsAlgorithm.INPUT_DATA, dataset_table)
    pca_statistics.SetColumnStatus('x', 1)
    pca_statistics.SetColumnStatus('y', 1)
    pca_statistics.RequestSelectedColumns()
    pca_statistics.SetDeriveOption(True)
    pca_statistics.update()

    ###### Eigenvalues ######
    eigenvalues = vtkDoubleArray()
    pca_statistics.GetEigenvalues(eigenvalues)
    for i in range(0, eigenvalues.number_of_tuples):
        print(f'Eigenvalue {i}  = {eigenvalues.GetValue(i):9.6f}')

    ###### Eigenvectors ######
    eigenvectors = vtkDoubleArray()
    pca_statistics.GetEigenvectors(eigenvectors)
    evec = [0] * eigenvectors.GetNumberOfComponents()
    for i in range(0, eigenvectors.number_of_tuples):
        eigenvectors.GetTuple(i, evec)
        s = f'Eigenvector {i} = ({fmt_floats(evec, w=0, d=6)})'
        print(s)

    evec1 = vtkDoubleArray()
    pca_statistics.GetEigenvector(0, evec1)

    evec2 = vtkDoubleArray()
    pca_statistics.GetEigenvector(1, evec2)

    scale = 3.0

    vector1_source = vtkLineSource()
    vector1_source.SetPoint1(0, 0, 0)
    vector1_source.SetPoint2(scale * evec1.GetValue(0), scale * evec1.GetValue(1), 0)

    vec1_mapper = vtkPolyDataMapper()
    vector1_source >> vec1_mapper

    vector1_actor = vtkActor(mapper=vec1_mapper)
    vector1_actor.property.color = colors.GetColor3d('LimeGreen')
    vector1_actor.property.line_width = 3

    vector2_source = vtkLineSource()
    vector2_source.SetPoint1(0, 0, 0)
    vector2_source.SetPoint2(scale * evec2.GetValue(0), scale * evec2.GetValue(1), 0)

    vec2_mapper = vtkPolyDataMapper()
    vector2_source >> vec2_mapper

    vector2_actor = vtkActor(mapper=vec2_mapper)
    vector2_actor.property.color = colors.GetColor3d('Crimson')
    vector2_actor.property.line_width = 3

    # Project all the points onto the eigenvector with the largest eigenvalues.
    p0 = [0.0] * 3
    p0[0] = -100 * evec1.GetValue(0)
    p0[1] = -100 * evec1.GetValue(1)
    p0[2] = 0
    p1 = [0.0] * 3
    p1[0] = 100 * evec1.GetValue(0)
    p1[1] = 100 * evec1.GetValue(1)
    p1[2] = 0

    projected_points = vtkPoints()
    for i in range(0, polydata.number_of_points):
        p = transform_filter.output.GetPoint(i)
        t = reference(0.0)
        closest_point = [0.0] * 3
        d = 0
        d = vtkLine.DistanceToLine(p, p0, p1, t, closest_point)
        # new_p = [0.0] * 3
        # v = [0.0] * 3
        # vtkMath.Subtract(p1, p0, v)
        # vtkMath.Normalize(v)
        # vtkMath.MultiplyScalar(v, t)
        # vtkMath.Add(p0, v, new_p)
        projected_points.InsertNextPoint(t, 0, 0)

    projected_poly_data = vtkPolyData(points=projected_points)

    projected_glyph_filter = vtkVertexGlyphFilter(input_data=projected_poly_data)
    projected_glyph_filter.update()

    projected_mapper = vtkPolyDataMapper()
    projected_glyph_filter >> projected_mapper
    projected_actor = vtkActor(mapper=projected_mapper)
    projected_actor.property.point_size = 2
    projected_actor.property.color = colors.GetColor3d('Gold')

    glyph_filter = vtkVertexGlyphFilter()
    transform_filter >> glyph_filter
    glyph_filter.update()

    original_mapper = vtkPolyDataMapper()
    glyph_filter >> original_mapper
    original_actor = vtkActor(mapper=original_mapper)
    original_actor.property.point_size = 3
    original_actor.property.color = colors.GetColor3d('Blue')

    # Map the points to spheres
    sphere_actor = point_to_glyph(transform_filter.output.GetPoints(), 0.007)
    sphere_actor.property.color = colors.GetColor3d('Blue')

    # Set up the render window, interactor and renderers.
    render_window = vtkRenderWindow(size=(600, 300), window_name='PCADemo')
    interactor = vtkRenderWindowInteractor()
    interactor.render_window = render_window

    # Define viewport ranges
    # (xmin, ymin, xmax, ymax)
    left_viewport = (0.0, 0.0, 0.5, 1.0)
    right_viewport = (0.5, 0.0, 1.0, 1.0)

    # Setup both renderers
    leftRenderer = vtkRenderer(viewport=left_viewport, background=colors.GetColor3d('Burlywood'))
    render_window.AddRenderer(leftRenderer)
    right_renderer = vtkRenderer(viewport=right_viewport, background=colors.GetColor3d('SlateGray'))
    render_window.AddRenderer(right_renderer)

    # leftRenderer.AddActor(original_actor)
    leftRenderer.AddActor(sphere_actor)
    leftRenderer.AddActor(vector1_actor)
    leftRenderer.AddActor(vector2_actor)

    right_renderer.AddActor(projected_actor)

    leftRenderer.ResetCamera()
    right_renderer.ResetCamera()

    render_window.Render()
    interactor.Start()


def fmt_floats(v, w=0, d=6, pt='f'):
    """
    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])


def point_to_glyph(points, scale):
    bounds = points.GetBounds()
    max_len = 0
    for i in range(1, 3):
        max_len = max(bounds[i + 1] - bounds[i], max_len)

    sphere_source = vtkSphereSource(radius=scale * max_len)

    pd = vtkPolyData(points=points)

    mapper = vtkGlyph3DMapper(input_data=pd, source_data=sphere_source.update().output, scalar_visibility=False,
                              scaling=False)
    actor = vtkActor(mapper=mapper)

    return actor


if __name__ == '__main__':
    main()