Skip to content

FroggieView

vtk-examples/Python/Visualization/FroggieView

Description

View surfaces of a segmented frog dataset using preprocessed *.vtk tissue files. This means that loading and processing is much faster when comapred with FroggieSurface.

FroggieView provides the ability to turn on and off surfaces, control their opacity through the use of sliders and control the camera position.

By default the frog is oriented so that we look down on the dorsal (posterior) surface and the superior surface faces the top of the screen. The frog is rendered with the skin being translucent so that you can see the internal organs.

In the lower left of the image there is a prop assembly with labelled XYZ axes and a cube labelled with anatomical orientations:

  • Sagittal plane
  • L - left
  • R - right
  • Coronal plane
  • A - anterior
  • P - posterior
  • Transverse plane
  • S - superior
  • I - inferior

This prop assembly can be moved and resized.

The opacity of each tissue is controlled by a slider, additionally you can turn all the sliders on or off by pressing the "n" key.

If the option "-n" is selected, no sliders will displayed.

Individual tissues can be specified by using the "-t" option e.g. "-t skin skeleton".

The parameters used to generate the example image are loaded from a JSON file containing the data needed to access and generate the actors for each tissue along with other supplementary data such as the data file names. This means that the user need only load this one file in order to generate the data for rendering. This file is called:

<DATA>/Frog_vtk.json

Where <DATA> is the path to vtk-examples/src/Testing/Data.

For information about the parameters in the JSON file, please see Frog_vtk_format.

The code uses a general way of specifying transformations that can permute image and other geometric data in order to maintain proper orientation regardless of the acquisition order. See the class SliceOrder.

The dataset was prepared at the Lawrence Berkeley National Laboratories. It is included with their permission. The data was acquired by physically slicing the frog and photographing the slices. The original segmented data is in the form of tissue masks with one file per tissue. There are 136 slices per tissue and 15 different tissues. Each slice is 470 by 500 pixels.

Further information:

Info

Mutually exclusive options "-a -b -c -d" are provided to let you generate approximations to the following figures: Figure 12-9a, Figure 12-9b, Figure 12-9c, and Figure 12-9d in Chapter 12 of the VTK Textbook.

Other languages

See (Cxx)

Question

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

Code

FroggieView.py

#!/usr/bin/env python3

import json
from pathlib import Path

# noinspection PyUnresolvedReferences
import vtkmodules.vtkInteractionStyle
# noinspection PyUnresolvedReferences
import vtkmodules.vtkRenderingOpenGL2
from vtkmodules.vtkCommonColor import vtkNamedColors
from vtkmodules.vtkCommonCore import (
    vtkCommand,
    vtkLookupTable
)
from vtkmodules.vtkCommonMath import vtkMatrix4x4
from vtkmodules.vtkCommonTransforms import vtkTransform
from vtkmodules.vtkFiltersCore import vtkPolyDataNormals
from vtkmodules.vtkFiltersGeneral import vtkTransformPolyDataFilter
from vtkmodules.vtkIOLegacy import vtkPolyDataReader
from vtkmodules.vtkInteractionStyle import vtkInteractorStyleTrackballCamera
from vtkmodules.vtkInteractionWidgets import (
    vtkCameraOrientationWidget,
    vtkOrientationMarkerWidget,
    vtkSliderRepresentation2D,
    vtkSliderWidget
)
from vtkmodules.vtkRenderingAnnotation import (
    vtkAxesActor,
    vtkAnnotatedCubeActor,
)
from vtkmodules.vtkRenderingCore import (
    vtkActor,
    vtkPolyDataMapper,
    vtkPropAssembly,
    vtkRenderWindow,
    vtkRenderWindowInteractor,
    vtkRenderer
)


def get_program_parameters(argv):
    import argparse
    description = 'View surfaces of a segmented frog dataset using preprocessed VTK tissue files.'
    epilogue = '''
Sliders are provided to control the opacity of the displayed tissues.
Up to fifteen different surfaces may be viewed.

Note:
   If you want to use brainbin (the brain with no gaussian smoothing),
    instead of brain, then request it with -t brainbin
    '''
    parser = argparse.ArgumentParser(description=description, epilog=epilogue,
                                     formatter_class=argparse.RawDescriptionHelpFormatter)

    group = parser.add_mutually_exclusive_group()
    group.add_argument('-a', action='store_const', dest='view', const='a',
                       help='The view corresponds to Fig 12-9a in the VTK Textbook')
    group.add_argument('-b', action='store_const', dest='view', const='b',
                       help='The view corresponds to Fig 12-9b in the VTK Textbook')
    group.add_argument('-c', action='store_const', dest='view', const='c',
                       help='The view corresponds to Fig 12-9c in the VTK Textbook')
    group.add_argument('-d', action='store_const', dest='view', const='d',
                       help='The view corresponds to Fig 12-9d in the VTK Textbook')
    group.add_argument('-l', action='store_const', dest='view', const='l',
                       help='The view corresponds to looking down on the anterior surface')
    group.add_argument('-p', action='store_const', dest='view', const='p',
                       help='The view corresponds to looking down on the posterior surface (the default)')
    parser.set_defaults(type=None)

    parser.add_argument('file_name', help='The path to the JSON file e.g. Frog_vtk.json.')
    parser.add_argument('-n', action='store_true', dest='omit_sliders', help='No sliders.')
    parser.add_argument('-t', nargs='+', dest='tissues', action='append', help='Select one or more tissues.')
    args = parser.parse_args()
    return args.file_name, args.view, args.omit_sliders, args.tissues


def main(fn, select_figure, no_sliders, chosen_tissues):
    if not select_figure:
        select_figure = 'p'

    fn_path = Path(fn)
    if not fn_path.suffix:
        fn_path = fn_path.with_suffix(".json")
    if not fn_path.is_file():
        print('Unable to find: ', fn_path)
    parsed_ok, parameters = parse_json(fn_path)
    if not parsed_ok:
        print('Unable to parse the JSON file.')
        return

    color_lut = create_tissue_lut(parameters['indices'], parameters['colors'])

    tissues = parameters['names']
    if select_figure:
        if select_figure == 'b':
            # No skin.
            tissues = parameters['fig12-9b']
        if select_figure in ['c', 'd']:
            # No skin, blood and skeleton.
            tissues = parameters['fig12-9cd']

    if chosen_tissues:
        chosen_tissues = [x.lower() for x in chosen_tissues[0]]
        res = list()
        has_brainbin = False
        if 'brainbin' in chosen_tissues:
            print('Using brainbin instead of brain.')
            res.append('brainbin')
            parameters['indices'].pop('brain', None)
            parameters['indices']['brainbin'] = 2
            parameters['colors'].pop('brain', None)
            parameters['colors']['brainbin'] = 'beige'
            has_brainbin = True
        for ct in chosen_tissues:
            if has_brainbin and ct in ['brain', 'brainbin']:
                continue
            if ct in tissues:
                res.append(ct)
            else:
                print(f'Tissue: {ct} is not available.')
                print(f'Available tissues are: {", ".join(tissues)} and brainbin')
                return
        if len(res) == 1 and 'skin' in res:
            parameters['opacity']['skin'] = 1.0
        tissues = res

    colors = vtkNamedColors()
    colors.SetColor("ParaViewBkg", [82, 87, 110, 255])

    # Setup render window, renderers, and interactor.
    ren = vtkRenderer()
    ren_win = vtkRenderWindow()
    ren_win.AddRenderer(ren)
    iren = vtkRenderWindowInteractor()
    iren.SetRenderWindow(ren_win)
    style = vtkInteractorStyleTrackballCamera()
    iren.SetInteractorStyle(style)

    sliders = dict()
    left_step_size = 1.0 / 9
    left_pos_y = 0.275
    left_pos_x0 = 0.02
    left_pos_x1 = 0.18
    right_step_size = 1.0 / 9
    right_pos_y = 0.05
    right_pos_x0 = 0.8 + 0.02
    right_pos_x1 = 0.8 + 0.18

    slider_count = 0

    color_size = len(max(parameters['colors'].values(), key=len))
    name_size = len(max(parameters['names'], key=len))
    int_size = 2
    line = '-' * (7 + name_size + color_size)
    res = [line,
           f'{"Tissue":<{name_size}s}{" Label "}{"Color"}',
           line]

    for tissue in tissues:
        reader = vtkPolyDataReader()
        reader.SetFileName(parameters['vtk_files'][tissue])
        reader.Update()

        trans = SliceOrder().get(parameters['orientation'][tissue])
        trans.Scale(1, -1, -1)

        tf = vtkTransformPolyDataFilter()
        tf.SetInputConnection(reader.GetOutputPort())
        tf.SetTransform(trans)
        tf.SetInputConnection(reader.GetOutputPort())

        normals = vtkPolyDataNormals()
        normals.SetInputConnection(tf.GetOutputPort())
        normals.SetFeatureAngle(60.0)

        mapper = vtkPolyDataMapper()
        mapper.SetInputConnection(normals.GetOutputPort())

        actor = vtkActor()
        actor.SetMapper(mapper)

        actor.GetProperty().SetOpacity(parameters['opacity'][tissue])
        actor.GetProperty().SetDiffuseColor(color_lut.GetTableValue(parameters['indices'][tissue])[:3])
        actor.GetProperty().SetSpecular(0.2)
        actor.GetProperty().SetSpecularPower(10)

        ren.AddActor(actor)

        if not no_sliders:
            slider_properties = SliderProperties()
            slider_properties.value_initial = parameters['opacity'][tissue]
            slider_properties.title = tissue

            # Screen coordinates.
            if slider_count < 7:
                slider_properties.p1 = [left_pos_x0, left_pos_y]
                slider_properties.p2 = [left_pos_x1, left_pos_y]
                left_pos_y += left_step_size
            else:
                slider_properties.p1 = [right_pos_x0, right_pos_y]
                slider_properties.p2 = [right_pos_x1, right_pos_y]
                right_pos_y += right_step_size

            slider_widget = make_slider_widget(slider_properties, color_lut, parameters['indices'][tissue])
            slider_widget.SetInteractor(iren)
            slider_widget.SetAnimationModeToAnimate()
            slider_widget.EnabledOn()

            cb = SliderCallback(actor.GetProperty())
            slider_widget.AddObserver(vtkCommand.InteractionEvent, cb)
            sliders[tissue] = slider_widget
            slider_count += 1

        res.append(
            f'{tissue:<{name_size}s} {parameters["indices"][tissue]:{int_size + 3}d}'
            f' {parameters["colors"][tissue]:<{color_size}s}')

    res.append(line)
    print('\n'.join(res))

    if no_sliders:
        ren_win.SetSize(1024, 1024)
    else:
        ren_win.SetSize(1024 + 400, 1024)
    ren_win.SetWindowName('FroggieView')

    ren.SetBackground(colors.GetColor3d('ParaViewBkg'))

    #  Final view.
    camera = ren.GetActiveCamera()
    # Superior Anterior Left
    labels = 'sal'
    if select_figure == 'a':
        # Fig 12-9a in the VTK Textbook
        camera.SetPosition(495.722368, -447.474954, -646.308030)
        camera.SetFocalPoint(137.612066, -40.962376, -195.171023)
        camera.SetViewUp(-0.323882, -0.816232, 0.478398)
        camera.SetDistance(704.996499)
        camera.SetClippingRange(319.797039, 1809.449285)
    elif select_figure == 'b':
        # Fig 12-9b in the VTK Textbook
        camera.SetPosition(478.683494, -420.477744, -643.112038)
        camera.SetFocalPoint(135.624874, -36.478435, -210.614440)
        camera.SetViewUp(-0.320495, -0.820148, 0.473962)
        camera.SetDistance(672.457328)
        camera.SetClippingRange(307.326771, 1765.990822)
    elif select_figure == 'c':
        # Fig 12-9c in the VTK Textbook
        camera.SetPosition(201.363313, -147.260834, -229.885066)
        camera.SetFocalPoint(140.626206, -75.857216, -162.352531)
        camera.SetViewUp(-0.425438, -0.786048, 0.448477)
        camera.SetDistance(115.534047)
        camera.SetClippingRange(7.109870, 854.091718)
    elif select_figure == 'd':
        # Fig 12-9d in the VTK Textbook
        camera.SetPosition(115.361727, -484.656410, -6.193827)
        camera.SetFocalPoint(49.126343, 98.501094, 1.323317)
        camera.SetViewUp(-0.649127, -0.083475, 0.756086)
        camera.SetDistance(586.955116)
        camera.SetClippingRange(360.549218, 866.876230)
    elif select_figure == 'l':
        # Orient so that we look down on the anterior surface and
        #   the superior surface faces the top of the screen.
        #  Left Superior Anterior
        labels = 'lsa'
        transform = vtkTransform()
        transform.SetMatrix(camera.GetModelTransformMatrix())
        transform.RotateY(90)
        transform.RotateZ(90)
        camera.SetModelTransformMatrix(transform.GetMatrix())
        ren.ResetCamera()
    else:
        # The default.
        # Orient so that we look down on the posterior surface and
        #   the superior surface faces the top of the screen.
        # Right Superior Posterior
        labels = 'rsp'
        transform = vtkTransform()
        transform.SetMatrix(camera.GetModelTransformMatrix())
        transform.RotateY(-90)
        transform.RotateZ(90)
        camera.SetModelTransformMatrix(transform.GetMatrix())
        ren.ResetCamera()

    cow = vtkCameraOrientationWidget()
    cow.SetParentRenderer(ren)
    if no_sliders:
        # Turn off if you do not want it.
        cow.On()
        cow.EnabledOn()
    else:
        cow.Off()
        cow.EnabledOff()

    axes = make_cube_actor(labels, colors)
    om = vtkOrientationMarkerWidget()
    om.SetOrientationMarker(axes)
    # Position upper left in the viewport.
    # om.SetViewport(0.0, 0.8, 0.2, 1.0)
    # Position lower left in the viewport.
    om.SetViewport(0, 0, 0.2, 0.2)
    om.SetInteractor(iren)
    om.EnabledOn()
    om.InteractiveOn()

    ren_win.Render()

    slider_toggle = SliderToggleCallback(sliders)
    iren.AddObserver('KeyPressEvent', slider_toggle)

    iren.Start()


def parse_json(fn_path):
    """
    Parse the JSON file selecting the components that we want.

    We also check that the file paths are valid.

    :param fn_path: The path the JSON file.
    :return: A dictionary of the parameters that we require.
    """
    with open(fn_path) as data_file:
        json_data = json.load(data_file)
    paths_ok = True
    parameters = dict()
    for k, v in json_data.items():
        if k == 'files':
            if 'root' in v:
                root = Path(v['root'])
                if not root.exists():
                    print(f'Bad path: {root}')
                    paths_ok = False
                else:
                    if 'vtk_files' not in v:
                        print('Expected vtk files.')
                        paths_ok = False
                        continue
                    for kk in v:
                        if kk == 'vtk_files':
                            if len(v[kk]) != 17:
                                print(f'Expected seventeen file names.')
                                paths_ok = False
                            # The stem of the file path becomes the key.
                            path_map = dict()
                            for p in list(map(lambda pp: root / pp, v[kk])):
                                path_map[p.stem] = p
                                if not p.is_file():
                                    paths_ok = False
                                    print(f'Not a file {p}')
                            if paths_ok:
                                parameters[kk] = path_map
            else:
                paths_ok = False
                print('Missing the key "root" and/or the key "files" for the files.')
        else:
            if k in ['tissues', 'figures']:
                for kk, vv in v.items():
                    parameters[kk] = vv
    return paths_ok, parameters


class SliceOrder:
    """
    These transformations permute image and other geometric data to maintain proper
     orientation regardless of the acquisition order. After applying these transforms with
    vtkTransformFilter, a view up of 0, -1, 0 will result in the body part
    facing the viewer.
    NOTE: some transformations have a -1 scale factor for one of the components.
          To ensure proper polygon orientation and normal direction, you must
          apply the vtkPolyDataNormals filter.

    Naming (the nomenclature is medical):
    si - superior to inferior (top to bottom)
    is - inferior to superior (bottom to top)
    ap - anterior to posterior (front to back)
    pa - posterior to anterior (back to front)
    lr - left to right
    rl - right to left
    """

    def __init__(self):
        self.si_mat = vtkMatrix4x4()
        self.si_mat.Zero()
        self.si_mat.SetElement(0, 0, 1)
        self.si_mat.SetElement(1, 2, 1)
        self.si_mat.SetElement(2, 1, -1)
        self.si_mat.SetElement(3, 3, 1)

        self.is_mat = vtkMatrix4x4()
        self.is_mat.Zero()
        self.is_mat.SetElement(0, 0, 1)
        self.is_mat.SetElement(1, 2, -1)
        self.is_mat.SetElement(2, 1, -1)
        self.is_mat.SetElement(3, 3, 1)

        self.lr_mat = vtkMatrix4x4()
        self.lr_mat.Zero()
        self.lr_mat.SetElement(0, 2, -1)
        self.lr_mat.SetElement(1, 1, -1)
        self.lr_mat.SetElement(2, 0, 1)
        self.lr_mat.SetElement(3, 3, 1)

        self.rl_mat = vtkMatrix4x4()
        self.rl_mat.Zero()
        self.rl_mat.SetElement(0, 2, 1)
        self.rl_mat.SetElement(1, 1, -1)
        self.rl_mat.SetElement(2, 0, 1)
        self.rl_mat.SetElement(3, 3, 1)

        """
        The previous transforms assume radiological views of the slices
         (viewed from the feet).
        Other modalities such as physical sectioning may view from the head.
        The following transforms modify the original with a 180° rotation about y
        """

        self.hf_mat = vtkMatrix4x4()
        self.hf_mat.Zero()
        self.hf_mat.SetElement(0, 0, -1)
        self.hf_mat.SetElement(1, 1, 1)
        self.hf_mat.SetElement(2, 2, -1)
        self.hf_mat.SetElement(3, 3, 1)

        self.transform = dict()

        si_trans = vtkTransform()
        si_trans.SetMatrix(self.si_mat)
        self.transform['si'] = si_trans

        is_trans = vtkTransform()
        is_trans.SetMatrix(self.is_mat)
        self.transform['is'] = is_trans

        ap_trans = vtkTransform()
        ap_trans.Scale(1, -1, 1)
        self.transform['ap'] = ap_trans

        pa_trans = vtkTransform()
        pa_trans.Scale(1, -1, -1)
        self.transform['pa'] = pa_trans

        lr_trans = vtkTransform()
        lr_trans.SetMatrix(self.lr_mat)
        self.transform['lr'] = lr_trans

        rl_trans = vtkTransform()
        rl_trans.SetMatrix(self.rl_mat)
        self.transform['rl'] = rl_trans

        hf_trans = vtkTransform()
        hf_trans.SetMatrix(self.hf_mat)
        self.transform['hf'] = hf_trans

        hf_si_trans = vtkTransform()
        hf_si_trans.SetMatrix(self.hf_mat)
        hf_si_trans.Concatenate(self.si_mat)
        self.transform['hfsi'] = hf_si_trans

        hf_is_trans = vtkTransform()
        hf_is_trans.SetMatrix(self.hf_mat)
        hf_is_trans.Concatenate(self.is_mat)
        self.transform['hfis'] = hf_is_trans

        hf_ap_trans = vtkTransform()
        hf_ap_trans.SetMatrix(self.hf_mat)
        hf_ap_trans.Scale(1, -1, 1)
        self.transform['hfap'] = hf_ap_trans

        hf_pa_trans = vtkTransform()
        hf_pa_trans.SetMatrix(self.hf_mat)
        hf_pa_trans.Scale(1, -1, -1)
        self.transform['hfpa'] = hf_pa_trans

        hf_lr_trans = vtkTransform()
        hf_lr_trans.SetMatrix(self.hf_mat)
        hf_lr_trans.Concatenate(self.lr_mat)
        self.transform['hflr'] = hf_lr_trans

        hf_rl_trans = vtkTransform()
        hf_rl_trans.SetMatrix(self.hf_mat)
        hf_rl_trans.Concatenate(self.rl_mat)
        self.transform['hfrl'] = hf_rl_trans

        # Identity
        self.transform['I'] = vtkTransform()

        # Zero
        z_trans = vtkTransform()
        z_trans.Scale(0, 0, 0)
        self.transform['Z'] = z_trans

    def print_transform(self, order):
        """
        Print the homogenous matrix corresponding to the slice order.
        :param order: The slice order.
        :return:
        """
        print(order)
        m = self.transform[order].GetMatrix()
        for i in range(0, 4):
            row = list()
            for j in range(0, 4):
                row.append(f'{m.GetElement(i, j):6.2g}')
            print(' '.join(row))

    def print_all_transforms(self):
        """
        Print all the homogenous matrices corresponding to the slice orders.
        :return:
        """
        for k in self.transform.keys():
            self.print_transform(k)

    def get(self, order):
        """
        Returns the vtkTransform corresponding to the slice order.

        :param order: The slice order.
        :return: The vtkTransform to use.
        """
        if order in self.transform.keys():
            return self.transform[order]
        else:
            s = 'No such transform "{:s}" exists.'.format(order)
            raise Exception(s)


def create_tissue_lut(indices, colors):
    """
    Create the lookup table for the frog tissues.

    Each table value corresponds the color of one of the frog tissues.

    :param indices: The tissue name and index.
    :param colors: The tissue name and color.
    :return: The lookup table.
    """
    lut = vtkLookupTable()
    lut.SetNumberOfColors(len(colors))
    lut.SetTableRange(0, len(colors) - 1)
    lut.Build()

    nc = vtkNamedColors()

    for k in indices.keys():
        lut.SetTableValue(indices[k], nc.GetColor4d(colors[k]))

    return lut


def make_axes_actor(scale, xyz_labels):
    """
    :param scale: Sets the scale and direction of the axes.
    :param xyz_labels: Labels for the axes.
    :return: The axes actor.
    """
    axes = vtkAxesActor()
    axes.SetScale(scale)
    axes.SetShaftTypeToCylinder()
    axes.SetXAxisLabelText(xyz_labels[0])
    axes.SetYAxisLabelText(xyz_labels[1])
    axes.SetZAxisLabelText(xyz_labels[2])
    axes.SetCylinderRadius(0.5 * axes.GetCylinderRadius())
    axes.SetConeRadius(1.025 * axes.GetConeRadius())
    axes.SetSphereRadius(1.5 * axes.GetSphereRadius())
    tprop = axes.GetXAxisCaptionActor2D().GetCaptionTextProperty()
    tprop.ItalicOn()
    tprop.ShadowOn()
    tprop.SetFontFamilyToTimes()
    # Use the same text properties on the other two axes.
    axes.GetYAxisCaptionActor2D().GetCaptionTextProperty().ShallowCopy(tprop)
    axes.GetZAxisCaptionActor2D().GetCaptionTextProperty().ShallowCopy(tprop)
    return axes


def make_annotated_cube_actor(cube_labels, colors):
    """
    :param cube_labels: The labels for the cube faces.
    :param colors: Used to determine the cube color.
    :return: The annotated cube actor.
    """
    # A cube with labeled faces.
    cube = vtkAnnotatedCubeActor()
    cube.SetXPlusFaceText(cube_labels[0])
    cube.SetXMinusFaceText(cube_labels[1])
    cube.SetYPlusFaceText(cube_labels[2])
    cube.SetYMinusFaceText(cube_labels[3])
    cube.SetZPlusFaceText(cube_labels[4])
    cube.SetZMinusFaceText(cube_labels[5])
    cube.SetFaceTextScale(0.5)
    cube.GetCubeProperty().SetColor(colors.GetColor3d('Gainsboro'))

    cube.GetTextEdgesProperty().SetColor(colors.GetColor3d('LightSlateGray'))

    # Change the vector text colors.
    cube.GetXPlusFaceProperty().SetColor(colors.GetColor3d('Tomato'))
    cube.GetXMinusFaceProperty().SetColor(colors.GetColor3d('Tomato'))
    cube.GetYPlusFaceProperty().SetColor(colors.GetColor3d('DeepSkyBlue'))
    cube.GetYMinusFaceProperty().SetColor(colors.GetColor3d('DeepSkyBlue'))
    cube.GetZPlusFaceProperty().SetColor(colors.GetColor3d('SeaGreen'))
    cube.GetZMinusFaceProperty().SetColor(colors.GetColor3d('SeaGreen'))
    return cube


def make_cube_actor(label_selector, colors):
    """
    :param label_selector: The selector used to define labels for the axes and cube.
    :param colors: Used to set the colors of the cube faces.
    :return: The combined axes and annotated cube prop.
    """
    if label_selector == 'sal':
        # xyz_labels = ['S', 'A', 'L']
        xyz_labels = ['+X', '+Y', '+Z']
        cube_labels = ['S', 'I', 'A', 'P', 'L', 'R']
        scale = [1.5, 1.5, 1.5]
    elif label_selector == 'rsp':
        # xyz_labels = ['R', 'S', 'P']
        xyz_labels = ['+X', '+Y', '+Z']
        cube_labels = ['R', 'L', 'S', 'I', 'P', 'A']
        scale = [1.5, 1.5, 1.5]
    elif label_selector == 'lsa':
        # xyz_labels = ['R', 'S', 'P']
        xyz_labels = ['+X', '+Y', '+Z']
        cube_labels = ['L', 'R', 'S', 'I', 'A', 'P']
        scale = [1.5, 1.5, 1.5]
    else:
        xyz_labels = ['+X', '+Y', '+Z']
        cube_labels = ['+X', '-X', '+Y', '-Y', '+Z', '-Z']
        scale = [1.5, 1.5, 1.5]

    # We are combining a vtkAxesActor and a vtkAnnotatedCubeActor
    # into a vtkPropAssembly
    cube = make_annotated_cube_actor(cube_labels, colors)
    axes = make_axes_actor(scale, xyz_labels)

    # Combine orientation markers into one with an assembly.
    assembly = vtkPropAssembly()
    assembly.AddPart(axes)
    assembly.AddPart(cube)
    return assembly


class SliderProperties:
    tube_width = 0.004
    slider_length = 0.015
    slider_width = 0.008
    end_cap_length = 0.008
    end_cap_width = 0.02
    title_height = 0.02
    label_height = 0.02

    value_minimum = 0.0
    value_maximum = 1.0
    value_initial = 1.0

    p1 = [0.02, 0.1]
    p2 = [0.18, 0.1]

    title = None

    title_color = 'Black'
    label_color = 'Black'
    value_color = 'DarkSlateGray'
    slider_color = 'BurlyWood'
    selected_color = 'Lime'
    bar_color = 'Black'
    bar_ends_color = 'Indigo'


def make_slider_widget(properties, lut, idx):
    """
    Make the slider widget.

    :param properties: The slider properties.
    :param lut: The color lookup table.
    :param idx: The tissue index.
    :return: The slider widget.
    """
    slider = vtkSliderRepresentation2D()

    slider.SetMinimumValue(properties.value_minimum)
    slider.SetMaximumValue(properties.value_maximum)
    slider.SetValue(properties.value_initial)
    slider.SetTitleText(properties.title)

    slider.GetPoint1Coordinate().SetCoordinateSystemToNormalizedDisplay()
    slider.GetPoint1Coordinate().SetValue(properties.p1[0], properties.p1[1])
    slider.GetPoint2Coordinate().SetCoordinateSystemToNormalizedDisplay()
    slider.GetPoint2Coordinate().SetValue(properties.p2[0], properties.p2[1])

    slider.SetTubeWidth(properties.tube_width)
    slider.SetSliderLength(properties.slider_length)
    slider.SetSliderWidth(properties.slider_width)
    slider.SetEndCapLength(properties.end_cap_length)
    slider.SetEndCapWidth(properties.end_cap_width)
    slider.SetTitleHeight(properties.title_height)
    slider.SetLabelHeight(properties.label_height)

    colors = vtkNamedColors()
    # Set the colors of the slider components.
    # Change the color of the bar.
    slider.GetTubeProperty().SetColor(colors.GetColor3d(properties.bar_color))
    # Change the color of the ends of the bar.
    slider.GetCapProperty().SetColor(colors.GetColor3d(properties.bar_ends_color))
    # Change the color of the knob that slides.
    slider.GetSliderProperty().SetColor(colors.GetColor3d(properties.slider_color))
    # Change the color of the knob when the mouse is held on it.
    slider.GetSelectedProperty().SetColor(colors.GetColor3d(properties.selected_color))
    # Change the color of the text displaying the value.
    slider.GetLabelProperty().SetColor(colors.GetColor3d(properties.value_color))
    #  Use the one color for the labels.
    # slider.GetTitleProperty().SetColor(colors.GetColor3d(properties.label_color))
    # Change the color of the text indicating what the slider controls
    if idx in range(0, 16):
        slider.GetTitleProperty().SetColor(lut.GetTableValue(idx)[:3])
        slider.GetTitleProperty().ShadowOff()
    else:
        slider.GetTitleProperty().SetColor(colors.GetColor3d(properties.title_color))

    slider_widget = vtkSliderWidget()
    slider_widget.SetRepresentation(slider)

    return slider_widget


class SliderCallback:
    def __init__(self, actor_property):
        self.actor_property = actor_property

    def __call__(self, caller, ev):
        slider_widget = caller
        value = slider_widget.GetRepresentation().GetValue()
        self.actor_property.SetOpacity(value)


class SliderToggleCallback:
    def __init__(self, sliders):
        self.sliders = sliders

    def __call__(self, caller, ev):
        if caller.GetKeyCode() == "n":
            for k, v in self.sliders.items():
                if v.GetEnabled():
                    v.Off()
                else:
                    v.On()


if __name__ == '__main__':
    import sys

    data_folder, view, omit_sliders, selected_tissues = get_program_parameters(sys.argv)
    main(data_folder, view, omit_sliders, selected_tissues)