code: from collections import Sized, OrderedDict
  import matplotlib.pyplot as plt
  from matplotlib import collections as mc
  import numpy as np
  
  import ipywidgets
  import IPython.display as ipydisplay
  
  from menpo.base import name_of_callable
  from menpo.image import MaskedImage, Image
  from menpo.image.base import _convert_patches_list_to_single_array
  from menpo.shape import TriMesh, ColouredTriMesh, TexturedTriMesh
  from menpo.visualize import print_dynamic
  from menpo.landmark import LandmarkManager
  
  from .options import (RendererOptionsWidget, TextPrintWidget,
                        SaveMatplotlibFigureOptionsWidget, AnimationOptionsWidget,
                        ImageOptionsWidget, LandmarkOptionsWidget,
                        PlotMatplotlibOptionsWidget, PatchOptionsWidget,
                        LinearModelParametersWidget, CameraSnapshotWidget,
                        Shape2DOptionsWidget, Shape3DOptionsWidget,
                        SaveMayaviFigureOptionsWidget, Mesh3DOptionsWidget)
  from .tools import LogoWidget, SwitchWidget
  from .utils import (extract_group_labels_from_landmarks,
                      extract_groups_labels_from_image, render_image,
                      render_patches)
  from .checks import check_n_parameters
  from .style import map_styles_to_hex_colours
  
  
  def menpowidgets_src_dir_path():
      r"""
      The path to the top of the menpowidgets package.
  
      Useful for locating where the logos folder is stored.
  
      Returns
      -------
      path : ``pathlib.Path``
          The full path to the top of the Menpo package
      """
      # to avoid cluttering the menpowidgets.base namespace
      from pathlib import Path
      import os.path
      return Path(os.path.abspath(__file__)).parent
  
  
  def visualize_shapes_2d(shapes, figure_size=(7, 7), browser_style='buttons',
                          custom_info_callback=None):
      r"""
      Widget that allows browsing through a `list` of
      2D shapes. The supported objects are:
  
              ================================== =
              Object
              ================================== =
              `menpo.shape.PointCloud`
              `menpo.shape.PointUndirectedGraph`
              `menpo.shape.PointDirectedGraph`
              `menpo.shape.PointTree`
              `menpo.shape.LabelledPointGraph`
              `menpo.shape.TriMesh`
              ================================== =
  
      Any instance of the above can be combined in the input `list`.
  
      Parameters
      ----------
      shapes : `list`
          The `list` of objects to be visualized. It can contain a combination of
  
              ================================== =
              Object
              ================================== =
              `menpo.shape.PointCloud`
              `menpo.shape.PointUndirectedGraph`
              `menpo.shape.PointDirectedGraph`
              `menpo.shape.PointTree`
              `menpo.shape.LabelledPointGraph`
              `menpo.shape.TriMesh`
              ================================== =
  
          or subclasses of those.
      figure_size : (`int`, `int`), optional
          The initial size of the rendered figure.
      browser_style : ``{'buttons', 'slider'}``, optional
          It defines whether the selector of the objects will have the form of
          plus/minus buttons or a slider.
      custom_info_callback: `function` or ``None``, optional
          If not ``None``, it should be a function that accepts a 2D shape
          and returns a list of custom messages to be printed about it. Each
          custom message will be printed in a separate line.
      """
      # Ensure that the code is being run inside a Jupyter kernel!
      from .utils import verify_ipython_and_kernel
      verify_ipython_and_kernel()
      print('Initializing...')
  
      # Make sure that shapes is a list even with one member
      if not isinstance(shapes, Sized):
          shapes = [shapes]
  
      # Get the number of shapes
      n_shapes = len(shapes)
  
      # Define the styling options
      main_style = 'warning'
  
      # Define render function
      def render_function(change):
          # Clear current figure, but wait until the generation of the new data
          # that will be rendered
          ipydisplay.clear_output(wait=True)
  
          # Get selected shape index
          i = shape_number_wid.selected_values if n_shapes > 1 else 0
  
          # Create options dictionary
          options = dict()
          options.update(shape_options_wid.selected_values['lines'])
          options.update(shape_options_wid.selected_values['markers'])
          options['image_view'] = shape_options_wid.selected_values['image_view']
          options.update(
              renderer_options_wid.selected_values['numbering_matplotlib'])
          options.update(renderer_options_wid.selected_values['axes'])
  
          # Correct options based on the type of the shape
          if hasattr(shapes[i], 'labels'):
              # If the shape is a LabelledPointUndirectedGraph ...
              # ...use the legend options
              options.update(renderer_options_wid.selected_values['legend'])
              # ...use with_labels
              options['with_labels'] = \
                  shape_options_wid.selected_values['with_labels']
              # ...correct colours
              line_colour = []
              marker_face_colour = []
              marker_edge_colour = []
              for lbl in options['with_labels']:
                  idx = shapes[i].labels.index(lbl)
                  line_colour.append(options['line_colour'][idx])
                  marker_face_colour.append(options['marker_face_colour'][idx])
                  marker_edge_colour.append(options['marker_edge_colour'][idx])
              options['line_colour'] = line_colour
              options['marker_face_colour'] = marker_face_colour
              options['marker_edge_colour'] = marker_edge_colour
          else:
              # If shape is PointCloud, TriMesh or PointGraph
              # ...correct colours
              options['line_colour'] = options['line_colour'][0]
              options['marker_face_colour'] = options['marker_face_colour'][0]
              options['marker_edge_colour'] = options['marker_edge_colour'][0]
  
          # Get figure size
          new_figure_size = (
              renderer_options_wid.selected_values['zoom_one'] * figure_size[0],
              renderer_options_wid.selected_values['zoom_one'] * figure_size[1])
  
          # Render shape with selected options
          save_figure_wid.renderer = shapes[i].view(
              figure_id=save_figure_wid.renderer.figure_id, new_figure=False,
              figure_size=new_figure_size, **options)
  
          # Force rendering
          save_figure_wid.renderer.force_draw()
  
          # Update info text widget
          update_info(shapes[i], custom_info_callback=custom_info_callback)
  
      # Define function that updates the info text
      def update_info(shape, custom_info_callback=None):
          min_b, max_b = shape.bounds()
          rang = shape.range()
          cm = shape.centre()
          text_per_line = [
              "> {}".format(name_of_callable(shape)),
              "> {} points".format(shape.n_points),
              "> Bounds: [{0:.1f}-{1:.1f}]W, [{2:.1f}-{3:.1f}]H".format(
                  min_b[0], max_b[0], min_b[1], max_b[1]),
              "> Range: {0:.1f}W, {1:.1f}H".format(rang[0], rang[1]),
              "> Centre of mass: ({0:.1f}, {1:.1f})".format(cm[0], cm[1]),
              "> Norm: {0:.2f}".format(shape.norm())]
          if custom_info_callback is not None:
              # iterate over the list of messages returned by the callback
              # function and append them in the text_per_line.
              for msg in custom_info_callback(shape):
                  text_per_line.append('> {}'.format(msg))
          info_wid.set_widget_state(text_per_line=text_per_line)
  
      # If the object is a LabelledPointUndirectedGraph, grab the labels
      labels = None
      if hasattr(shapes[0], 'labels'):
          labels = shapes[0].labels
  
      # Create widgets
      shape_options_wid = Shape2DOptionsWidget(
          labels=labels, render_function=render_function)
      renderer_options_wid = RendererOptionsWidget(
          options_tabs=['zoom_one', 'axes', 'numbering_matplotlib', 'legend'],
          labels=None,  axes_x_limits=0.1, axes_y_limits=0.1,
          render_function=render_function)
      info_wid = TextPrintWidget(text_per_line=[''])
      save_figure_wid = SaveMatplotlibFigureOptionsWidget()
  
      # Group widgets
      if n_shapes > 1:
          # Define function that updates options' widgets state
          def update_widgets(change):
              # Get current shape and check if it has labels
              i = change['new']
              labels = None
              if hasattr(shapes[i], 'labels'):
                  labels = shapes[i].labels
  
              # Update shape options
              shape_options_wid.set_widget_state(labels=labels,
                                                 allow_callback=True)
  
          # Shape selection slider
          index = {'min': 0, 'max': n_shapes-1, 'step': 1, 'index': 0}
          shape_number_wid = AnimationOptionsWidget(
              index, render_function=update_widgets, index_style=browser_style,
              interval=0.2, description='Shape', loop_enabled=True,
              continuous_update=False)
  
          # Header widget
          logo_wid = LogoWidget(style=main_style)
          logo_wid.layout.margin = '0px 10px 0px 0px'
          header_wid = ipywidgets.HBox([logo_wid, shape_number_wid])
          header_wid.layout.align_items = 'center'
          header_wid.layout.margin = '0px 0px 10px 0px'
      else:
          # Header widget
          header_wid = LogoWidget(style=main_style)
          header_wid.layout.margin = '0px 10px 0px 0px'
      options_box = ipywidgets.Tab(
          [info_wid, shape_options_wid, renderer_options_wid, save_figure_wid])
      tab_titles = ['Info', 'Shape', 'Renderer', 'Export']
      for (k, tl) in enumerate(tab_titles):
          options_box.set_title(k, tl)
      if n_shapes > 1:
          wid = ipywidgets.VBox([header_wid, options_box])
      else:
          wid = ipywidgets.HBox([header_wid, options_box])
  
      # Set widget's style
      wid.box_style = main_style
      wid.layout.border = '2px solid ' + map_styles_to_hex_colours(main_style)
  
      # Display final widget
      final_box = ipywidgets.Box([wid])
      final_box.layout.display = 'flex'
      ipydisplay.display(final_box)
  
      # Trigger initial visualization
      render_function({})
  
  
  def visualize_shapes_3d(shapes, browser_style='buttons',
                          custom_info_callback=None):
      r"""
      Widget that allows browsing through a `list` of
      3D shapes. The supported objects are:
  
              ==================================
              Object
              ==================================
              `menpo.shape.PointCloud`
              `menpo.shape.PointUndirectedGraph`
              `menpo.shape.PointDirectedGraph`
              `menpo.shape.PointTree`
              `menpo.shape.LabelledPointGraph`
              ==================================
  
      Any instance of the above can be combined in the input `list`.
  
      Parameters
      ----------
      shapes : `list`
          The `list` of objects to be visualized. It can contain a combination of
  
              ==================================
              Object
              ==================================
              `menpo.shape.PointCloud`
              `menpo.shape.PointUndirectedGraph`
              `menpo.shape.PointDirectedGraph`
              `menpo.shape.PointTree`
              `menpo.shape.LabelledPointGraph`
              ==================================
  
          or subclasses of those.
      browser_style : ``{'buttons', 'slider'}``, optional
          It defines whether the selector of the objects will have the form of
          plus/minus buttons or a slider.
      custom_info_callback: `function` or ``None``, optional
          If not ``None``, it should be a function that accepts a 2D shape
          and returns a list of custom messages to be printed about it. Each
          custom message will be printed in a separate line.
      """
      # Ensure that the code is being run inside a Jupyter kernel!
      from .utils import verify_ipython_and_kernel
      verify_ipython_and_kernel()
      print_dynamic('Initializing...')
  
      # Make sure that shapes is a list even with one member
      if not isinstance(shapes, Sized):
          shapes = [shapes]
  
      # Get the number of shapes
      n_shapes = len(shapes)
  
      # Define the styling options
      main_style = 'warning'
  
      # Define render function
      def render_function(change):
          # Clear current figure
          save_figure_wid.renderer.clear_figure()
          ipydisplay.clear_output(wait=True)
  
          # Get selected shape index
          i = shape_number_wid.selected_values if n_shapes > 1 else 0
  
          # Update info text widget
          update_info(shapes[i], custom_info_callback=custom_info_callback)
  
          # Create options dictionary
          options = dict()
          if isinstance(shapes[i], TriMesh):
              # Note that 3D TriMesh has a totally different set of options
              # compared to any other PointCloud or PointGraph. However, in order
              # for visualize_shapes_3d to support TriMeshes, we simply use the
              # options that are common. This means that most of the widget's
              # options will have no effect on rendering...
              options['mesh_type'] = 'wireframe'
              if shape_options_wid.selected_values['markers']['render_markers']:
                  options['mesh_type'] = 'fancymesh'
              options['line_width'] = \
                  shape_options_wid.selected_values['lines']['line_width']
              options['colour'] = \
                  shape_options_wid.selected_values['lines']['line_colour'][0]
              options['marker_style'] = \
                  shape_options_wid.selected_values['markers']['marker_style']
              options['marker_size'] = \
                  shape_options_wid.selected_values['markers']['marker_size']
              options['marker_resolution'] = \
                  shape_options_wid.selected_values['markers']['marker_resolution']
              options['step'] = \
                  shape_options_wid.selected_values['markers']['step']
          else:
              options.update(shape_options_wid.selected_values['lines'])
              options.update(shape_options_wid.selected_values['markers'])
              options.update(
                  renderer_options_wid.selected_values['numbering_mayavi'])
  
              # Correct options based on the type of the shape
              if hasattr(shapes[i], 'labels'):
                  # If the shape is a LabelledPointUndirectedGraph ...
                  # ...use with_labels
                  options['with_labels'] = \
                      shape_options_wid.selected_values['with_labels']
                  # ...correct colours
                  line_colour = []
                  marker_colour = []
                  for lbl in options['with_labels']:
                      idx = shapes[i].labels.index(lbl)
                      line_colour.append(options['line_colour'][idx])
                      marker_colour.append(options['marker_colour'][idx])
                  options['line_colour'] = line_colour
                  options['marker_colour'] = marker_colour
              else:
                  # If shape is PointCloud, TriMesh or PointGraph
                  # ...correct colours
                  options['line_colour'] = options['line_colour'][0]
                  options['marker_colour'] = options['marker_colour'][0]
  
          # Render shape with selected options
          save_figure_wid.renderer = shapes[i].view(
              figure_id=save_figure_wid.renderer.figure_id, new_figure=False,
              alpha=1.0, **options)
  
          # Force rendering
          save_figure_wid.renderer.force_draw()
  
      # Define function that updates the info text
      def update_info(shape, custom_info_callback=None):
          min_b, max_b = shape.bounds()
          rang = shape.range()
          cm = shape.centre()
          text_per_line = [
              "> {}".format(name_of_callable(shape)),
              "> {} points".format(shape.n_points),
              "> Bounds: [{0:.1f}-{1:.1f}]X, [{2:.1f}-{3:.1f}]Y, "
              "[{4:.1f}-{5:.1f}]Z".format(min_b[0], max_b[0], min_b[1], max_b[1],
                                          min_b[2], max_b[2]),
              "> Range: {0:.1f}X, {1:.1f}Y, {2:.1f}Z".format(rang[0], rang[1],
                                                             rang[2]),
              "> Centre of mass: ({0:.1f}X, {1:.1f}Y, {2:.1f}Z)".format(
                  cm[0], cm[1], cm[2]),
              "> Norm: {0:.2f}".format(shape.norm())]
          if custom_info_callback is not None:
              # iterate over the list of messages returned by the callback
              # function and append them in the text_per_line.
              for msg in custom_info_callback(shape):
                  text_per_line.append('> {}'.format(msg))
          info_wid.set_widget_state(text_per_line=text_per_line)
  
      # If the object is a LabelledPointUndirectedGraph, grab the labels
      labels = None
      if hasattr(shapes[0], 'labels'):
          labels = shapes[0].labels
  
      # Create widgets
      shape_options_wid = Shape3DOptionsWidget(
          labels=labels, render_function=render_function)
      renderer_options_wid = RendererOptionsWidget(
          options_tabs=['numbering_mayavi'], labels=None,
          render_function=render_function)
      info_wid = TextPrintWidget(text_per_line=[''])
      save_figure_wid = SaveMayaviFigureOptionsWidget()
  
      # Group widgets
      if n_shapes > 1:
          # Define function that updates options' widgets state
          def update_widgets(change):
              # Get current shape and check if it has labels
              i = change['new']
              labels = None
              if hasattr(shapes[i], 'labels'):
                  labels = shapes[i].labels
  
              # Update shape options
              shape_options_wid.set_widget_state(labels=labels,
                                                 allow_callback=True)
  
          # Shape selection slider
          index = {'min': 0, 'max': n_shapes-1, 'step': 1, 'index': 0}
          shape_number_wid = AnimationOptionsWidget(
              index, render_function=update_widgets, index_style=browser_style,
              interval=0.2, description='Shape', loop_enabled=True,
              continuous_update=False)
  
          # Header widget
          logo_wid = LogoWidget(style=main_style)
          logo_wid.layout.margin = '0px 10px 0px 0px'
          header_wid = ipywidgets.HBox([logo_wid, shape_number_wid])
          header_wid.layout.align_items = 'center'
          header_wid.layout.margin = '0px 0px 10px 0px'
      else:
          # Header widget
          header_wid = LogoWidget(style=main_style)
          header_wid.layout.margin = '0px 10px 0px 0px'
      options_box = ipywidgets.Tab(
          [info_wid, shape_options_wid, renderer_options_wid, save_figure_wid])
      tab_titles = ['Info', 'Shape', 'Renderer', 'Export']
      for (k, tl) in enumerate(tab_titles):
          options_box.set_title(k, tl)
      if n_shapes > 1:
          wid = ipywidgets.VBox([header_wid, options_box])
      else:
          wid = ipywidgets.HBox([header_wid, options_box])
  
      # Set widget's style
      wid.box_style = main_style
      wid.layout.border = '2px solid ' + map_styles_to_hex_colours(main_style)
  
      # Display final widget
      final_box = ipywidgets.Box([wid])
      final_box.layout.display = 'flex'
      ipydisplay.display(final_box)
  
      # Trigger initial visualization
      render_function({})
      print_dynamic('')
  
  
  def visualize_landmarks_2d(landmarks, figure_size=(7, 7),
                             browser_style='buttons', custom_info_callback=None):
      r"""
      Widget that allows browsing through a `list` of
      `menpo.landmark.LandmarkManager` (or subclass) objects. The landmark
      managers can have a combination of different attributes, e.g.
      landmark groups and labels etc.
  
      Parameters
      ----------
      landmarks : `list` of `menpo.landmark.LandmarkManager` or subclass
          The `list` of landmark managers to be visualized.
      figure_size : (`int`, `int`), optional
          The initial size of the rendered figure.
      browser_style : ``{'buttons', 'slider'}``, optional
          It defines whether the selector of the objects will have the form of
          plus/minus buttons or a slider.
      custom_info_callback: `function` or ``None``, optional
          If not None, it should be a function that accepts a landmark group and
          returns a list of custom messages to be printed per landmark group.
          Each custom message will be printed in a separate line.
      """
      # Ensure that the code is being run inside a Jupyter kernel!
      from .utils import verify_ipython_and_kernel
      verify_ipython_and_kernel()
      print('Initializing...')
  
      # Make sure that landmarks is a list even with one landmark manager member
      if isinstance(landmarks, LandmarkManager):
          landmarks = [landmarks]
  
      # Get the number of landmark managers
      n_landmarks = len(landmarks)
  
      # Define the styling options
      main_style = 'info'
  
      # Define render function
      def render_function(change):
          # Clear current figure, but wait until the generation of the new data
          # that will be rendered
          ipydisplay.clear_output(wait=True)
  
          # get selected index and selected group
          i = landmark_number_wid.selected_values if n_landmarks > 1 else 0
          g = landmark_options_wid.selected_values['landmarks']['group']
  
          if landmark_options_wid.selected_values['landmarks']['render_landmarks']:
              # get shape
              shape = landmarks[i][g]
  
              # Create options dictionary
              options = dict()
              options.update(landmark_options_wid.selected_values['lines'])
              options.update(landmark_options_wid.selected_values['markers'])
              options['image_view'] = landmark_options_wid.selected_values['image_view']
              options.update(
                  renderer_options_wid.selected_values['numbering_matplotlib'])
              options.update(renderer_options_wid.selected_values['axes'])
  
              # Correct options based on the type of the shape
              if hasattr(shape, 'labels'):
                  # If the shape is a LabelledPointUndirectedGraph ...
                  # ...use the legend options
                  options.update(renderer_options_wid.selected_values['legend'])
                  # ...use with_labels
                  options['with_labels'] = \
                      landmark_options_wid.selected_values['landmarks']['with_labels']
                  # ...correct colours
                  line_colour = []
                  marker_face_colour = []
                  marker_edge_colour = []
                  for lbl in options['with_labels']:
                      id = shape.labels.index(lbl)
                      line_colour.append(options['line_colour'][id])
                      marker_face_colour.append(options['marker_face_colour'][id])
                      marker_edge_colour.append(options['marker_edge_colour'][id])
                  options['line_colour'] = line_colour
                  options['marker_face_colour'] = marker_face_colour
                  options['marker_edge_colour'] = marker_edge_colour
              else:
                  # If shape is PointCloud, TriMesh or PointGraph
                  # ...correct colours
                  options['line_colour'] = options['line_colour'][0]
                  options['marker_face_colour'] = options['marker_face_colour'][0]
                  options['marker_edge_colour'] = options['marker_edge_colour'][0]
  
              # Get figure size
              new_figure_size = (
                  renderer_options_wid.selected_values['zoom_one'] *
                  figure_size[0],
                  renderer_options_wid.selected_values['zoom_one'] *
                  figure_size[1])
  
              # Render shape with selected options
              save_figure_wid.renderer = shape.view(
                  figure_id=save_figure_wid.renderer.figure_id, new_figure=False,
                  figure_size=new_figure_size, **options)
  
              # Force rendering
              save_figure_wid.renderer.force_draw()
          else:
              ipydisplay.clear_output()
  
          # update info text widget
          update_info(landmarks[i], g, custom_info_callback=custom_info_callback)
  
      # Define function that updates the info text
      def update_info(landmarks, group, custom_info_callback=None):
          if group is not None:
              min_b, max_b = landmarks[group].bounds()
              rang = landmarks[group].range()
              cm = landmarks[group].centre()
              text_per_line = [
                  "> {} landmark points".format(landmarks[group].n_points),
                  "> {}".format(name_of_callable(landmarks[group])),
                  "> Bounds: [{0:.1f}-{1:.1f}]W, [{2:.1f}-{3:.1f}]H".format(
                      min_b[0], max_b[0], min_b[1], max_b[1]),
                  "> Range: {0:.1f}W, {1:.1f}H".format(rang[0], rang[1]),
                  "> Centre of mass: ({0:.1f}, {1:.1f})".format(cm[0], cm[1]),
                  "> Norm: {0:.2f}".format(landmarks[group].norm())]
              if custom_info_callback is not None:
                  # iterate over the list of messages returned by the callback
                  # function and append them in the text_per_line.
                  for msg in custom_info_callback(landmarks[group]):
                      text_per_line.append('> {}'.format(msg))
          else:
              text_per_line = ["No landmarks available."]
  
          info_wid.set_widget_state(text_per_line=text_per_line)
  
      # Create widgets
      groups_keys, labels_keys = extract_group_labels_from_landmarks(landmarks[0])
      first_label = labels_keys[0] if labels_keys else None
      landmark_options_wid = LandmarkOptionsWidget(
          group_keys=groups_keys, labels_keys=labels_keys,
          type='2D', render_function=render_function)
      renderer_options_wid = RendererOptionsWidget(
          options_tabs=['zoom_one', 'axes', 'numbering_matplotlib', 'legend'],
          labels=first_label, axes_x_limits=0.1, axes_y_limits=0.1,
          render_function=render_function)
      info_wid = TextPrintWidget(text_per_line=[''])
      save_figure_wid = SaveMatplotlibFigureOptionsWidget()
  
      # Group widgets
      if n_landmarks > 1:
          # Define function that updates options' widgets state
          def update_widgets(change):
              # Get new groups and labels
              i = landmark_number_wid.selected_values
              g_keys, l_keys = extract_group_labels_from_landmarks(
                  landmarks[i])
  
              # Update landmarks options
              landmark_options_wid.set_widget_state(
                  group_keys=g_keys, labels_keys=l_keys, allow_callback=True)
  
          # Landmark selection slider
          index = {'min': 0, 'max': n_landmarks-1, 'step': 1, 'index': 0}
          landmark_number_wid = AnimationOptionsWidget(
              index, render_function=update_widgets, index_style=browser_style,
              interval=0.2, description='Shape', loop_enabled=True,
              continuous_update=False)
  
          # Header widget
          logo_wid = LogoWidget(style=main_style)
          logo_wid.layout.margin = '0px 10px 0px 0px'
          header_wid = ipywidgets.HBox([logo_wid, landmark_number_wid])
          header_wid.layout.align_items = 'center'
          header_wid.layout.margin = '0px 0px 10px 0px'
      else:
          # Header widget
          header_wid = LogoWidget(style=main_style)
          header_wid.layout.margin = '0px 10px 0px 0px'
      options_box = ipywidgets.Tab(
          children=[info_wid, landmark_options_wid, renderer_options_wid,
                    save_figure_wid])
      tab_titles = ['Info', 'Landmarks', 'Renderer', 'Export']
      for (k, tl) in enumerate(tab_titles):
          options_box.set_title(k, tl)
      if n_landmarks > 1:
          wid = ipywidgets.VBox([header_wid, options_box])
      else:
          wid = ipywidgets.HBox([header_wid, options_box])
  
      # Set widget's style
      wid.box_style = main_style
      wid.layout.border = '2px solid ' + map_styles_to_hex_colours(main_style)
  
      # Display final widget
      final_box = ipywidgets.Box([wid])
      final_box.layout.display = 'flex'
      ipydisplay.display(final_box)
  
      # Trigger initial visualization
      render_function({})
  
  
  def visualize_landmarks_3d(landmarks, browser_style='buttons',
                             custom_info_callback=None):
      r"""
      Widget that allows browsing through a `list` of
      `menpo.landmark.LandmarkManager` (or subclass) objects. The landmark
      managers can have a combination of different attributes, e.g.
      landmark groups and labels etc.
  
      Parameters
      ----------
      landmarks : `list` of `menpo.landmark.LandmarkManager` or subclass
          The `list` of landmark managers to be visualized.
      browser_style : ``{'buttons', 'slider'}``, optional
          It defines whether the selector of the objects will have the form of
          plus/minus buttons or a slider.
      custom_info_callback: `function` or ``None``, optional
          If not None, it should be a function that accepts a landmark group and
          returns a list of custom messages to be printed per landmark group.
          Each custom message will be printed in a separate line.
      """
      # Ensure that the code is being run inside a Jupyter kernel!
      from .utils import verify_ipython_and_kernel
      verify_ipython_and_kernel()
      print('Initializing...')
  
      # Make sure that landmarks is a list even with one landmark manager member
      if not isinstance(landmarks, list):
          landmarks = [landmarks]
  
      # Get the number of landmark managers
      n_landmarks = len(landmarks)
  
      # Define the styling options
      main_style = 'info'
  
      # Define render function
      def render_function(change):
          # Clear current figure
          save_figure_wid.renderer.clear_figure()
          ipydisplay.clear_output(wait=True)
  
          # get selected index and selected group
          i = landmark_number_wid.selected_values if n_landmarks > 1 else 0
          g = landmark_options_wid.selected_values['landmarks']['group']
  
          # update info text widget
          update_info(landmarks[i], g, custom_info_callback=custom_info_callback)
  
          if landmark_options_wid.selected_values['landmarks']['render_landmarks']:
              # get shape
              shape = landmarks[i][g]
  
              options = dict()
              if isinstance(shape, TriMesh):
                  # Note that 3D TriMesh has a totally different set of options
                  # compared to any other PointCloud or PointGraph. However, in
                  # order for visualize_landmarks_3d to support TriMeshes, we
                  # simply use the options that are common. This means that most
                  # of the widget's options will have no effect on rendering...
                  options['mesh_type'] = 'wireframe'
                  if landmark_options_wid.selected_values['markers'][
                      'render_markers']:
                      options['mesh_type'] = 'fancymesh'
                  options['line_width'] = \
                      landmark_options_wid.selected_values['lines']['line_width']
                  options['colour'] = \
                      landmark_options_wid.selected_values['lines']['line_colour'][0]
                  options['marker_style'] = \
                      landmark_options_wid.selected_values['markers']['marker_style']
                  options['marker_size'] = \
                      landmark_options_wid.selected_values['markers']['marker_size']
                  options['marker_resolution'] = \
                      landmark_options_wid.selected_values['markers'][
                          'marker_resolution']
                  options['step'] = \
                      landmark_options_wid.selected_values['markers']['step']
              else:
                  options.update(landmark_options_wid.selected_values['lines'])
                  options.update(landmark_options_wid.selected_values['markers'])
                  options.update(
                      renderer_options_wid.selected_values['numbering_mayavi'])
  
                  # Correct options based on the type of the shape
                  if hasattr(shape, 'labels'):
                      # If the shape is a LabelledPointUndirectedGraph ...
                      # ...use with_labels
                      options['with_labels'] = \
                          landmark_options_wid.selected_values['landmarks']['with_labels']
                      # ...correct colours
                      line_colour = []
                      marker_colour = []
                      for lbl in options['with_labels']:
                          idx = shape.labels.index(lbl)
                          line_colour.append(options['line_colour'][idx])
                          marker_colour.append(options['marker_colour'][idx])
                      options['line_colour'] = line_colour
                      options['marker_colour'] = marker_colour
                  else:
                      # If shape is PointCloud, TriMesh or PointGraph
                      # ...correct colours
                      options['line_colour'] = options['line_colour'][0]
                      options['marker_colour'] = options['marker_colour'][0]
  
              # Render shape with selected options
              save_figure_wid.renderer = shape.view(
                  figure_id=save_figure_wid.renderer.figure_id, new_figure=False,
                  alpha=1.0, **options)
  
              # Force rendering
              save_figure_wid.renderer.force_draw()
          else:
              ipydisplay.clear_output()
  
      # Define function that updates the info text
      def update_info(landmarks, group, custom_info_callback=None):
          if group is not None:
              min_b, max_b = landmarks[group].bounds()
              rang = landmarks[group].range()
              cm = landmarks[group].centre()
              text_per_line = [
                  "> {} landmark points".format(landmarks[group].n_points),
                  "> {}".format(name_of_callable(landmarks[group])),
                  "> Bounds: [{0:.1f}-{1:.1f}]X, [{2:.1f}-{3:.1f}]Y, "
                  "[{4:.1f}-{5:.1f}]Z".format(
                      min_b[0], max_b[0], min_b[1], max_b[1], min_b[2], max_b[2]),
                  "> Range: {0:.1f}X, {1:.1f}Y, {2:.1f}Z".format(rang[0], rang[1],
                                                                 rang[2]),
                  "> Centre of mass: ({0:.1f}X, {1:.1f}Y, {2:.1f}Z)".format(
                      cm[0], cm[1], cm[2]),
                  "> Norm: {0:.2f}".format(landmarks[group].norm())]
              if custom_info_callback is not None:
                  # iterate over the list of messages returned by the callback
                  # function and append them in the text_per_line.
                  for msg in custom_info_callback(landmarks[group]):
                      text_per_line.append('> {}'.format(msg))
          else:
              text_per_line = ["No landmarks available."]
  
          info_wid.set_widget_state(text_per_line=text_per_line)
  
      # Create widgets
      groups_keys, labels_keys = extract_group_labels_from_landmarks(landmarks[0])
      first_label = labels_keys[0] if labels_keys else None
      landmark_options_wid = LandmarkOptionsWidget(
          group_keys=groups_keys, labels_keys=labels_keys,
          type='3D', render_function=render_function)
      renderer_options_wid = RendererOptionsWidget(
          options_tabs=['numbering_mayavi'], labels=first_label,
          render_function=render_function)
      info_wid = TextPrintWidget(text_per_line=[''])
      save_figure_wid = SaveMayaviFigureOptionsWidget()
  
      # Group widgets
      if n_landmarks > 1:
          # Define function that updates options' widgets state
          def update_widgets(change):
              # Get new groups and labels
              i = landmark_number_wid.selected_values
              g_keys, l_keys = extract_group_labels_from_landmarks(
                  landmarks[i])
  
              # Update landmarks options
              landmark_options_wid.set_widget_state(
                  group_keys=g_keys, labels_keys=l_keys, allow_callback=True)
  
          # Landmark selection slider
          index = {'min': 0, 'max': n_landmarks-1, 'step': 1, 'index': 0}
          landmark_number_wid = AnimationOptionsWidget(
              index, render_function=update_widgets, index_style=browser_style,
              interval=0.2, description='Shape', loop_enabled=True,
              continuous_update=False)
  
          # Header widget
          logo_wid = LogoWidget(style=main_style)
          logo_wid.layout.margin = '0px 10px 0px 0px'
          header_wid = ipywidgets.HBox([logo_wid, landmark_number_wid])
          header_wid.layout.align_items = 'center'
          header_wid.layout.margin = '0px 0px 10px 0px'
      else:
          # Header widget
          header_wid = LogoWidget(style=main_style)
          header_wid.layout.margin = '0px 10px 0px 0px'
      options_box = ipywidgets.Tab(
          children=[info_wid, landmark_options_wid, renderer_options_wid,
                    save_figure_wid])
      tab_titles = ['Info', 'Landmarks', 'Renderer', 'Export']
      for (k, tl) in enumerate(tab_titles):
          options_box.set_title(k, tl)
      if n_landmarks > 1:
          wid = ipywidgets.VBox([header_wid, options_box])
      else:
          wid = ipywidgets.HBox([header_wid, options_box])
  
      # Set widget's style
      wid.box_style = main_style
      wid.layout.border = '2px solid ' + map_styles_to_hex_colours(main_style)
  
      # Display final widget
      final_box = ipywidgets.Box([wid])
      final_box.layout.display = 'flex'
      ipydisplay.display(final_box)
  
      # Trigger initial visualization
      render_function({})
      print_dynamic('')
  
  
  def visualize_meshes_3d(meshes, browser_style='buttons',
                          custom_info_callback=None):
      r"""
      Widget that allows browsing through a `list` of 3D meshes. The supported
      objects are:
  
              ==================================
              Object
              ==================================
              `menpo.shape.TriMesh`
              `menpo.shape.ColouredTriMesdh`
              `menpo.shape.TexturedTriMesh`
              ==================================
  
      Any instance of the above can be combined in the input `list`.
  
      Parameters
      ----------
      meshes : `list`
          The `list` of objects to be visualized. It can contain a combination of
  
              ==================================
              Object
              ==================================
              `menpo.shape.TriMesh`
              `menpo.shape.ColouredTriMesdh`
              `menpo.shape.TexturedTriMesh`
              ==================================
  
          or subclasses of those.
      browser_style : ``{'buttons', 'slider'}``, optional
          It defines whether the selector of the objects will have the form of
          plus/minus buttons or a slider.
      custom_info_callback: `function` or ``None``, optional
          If not ``None``, it should be a function that accepts a 3D mesh
          and returns a list of custom messages to be printed about it. Each
          custom message will be printed in a separate line.
      """
      # Ensure that the code is being run inside a Jupyter kernel!!
      from menpowidgets.utils import verify_ipython_and_kernel
      verify_ipython_and_kernel()
      out = ipywidgets.Output()
      ipydisplay.display(out)
      with out:
          ipydisplay.clear_output(wait=True)
          print('Initializing...')
  
      # Make sure that meshes is a list even with one member
      if not isinstance(meshes, Sized):
          meshes = [meshes]
  
      # Get the number of meshes
      n_meshes = len(meshes)
  
      # Define the styling options
      main_style = 'warning'
  
      # Define render function
      def render_function(_):
          # Clear current figure
          save_figure_wid.renderer.clear_figure()
          with out:
              ipydisplay.clear_output(wait=True)
  
          # Get selected mesh index
          i = mesh_number_wid.selected_values if n_meshes > 1 else 0
  
          # Update info text widget
          update_info(meshes[i], custom_info_callback=custom_info_callback)
  
          # Render instance
          save_figure_wid.renderer = meshes[i].view(
              figure_id=save_figure_wid.renderer.figure_id, new_figure=False,
              **mesh_options_wid.selected_values)
  
          # Force rendering
          save_figure_wid.renderer.force_draw()
  
      # Define function that updates the info text
      def update_info(mesh, custom_info_callback=None):
          min_b, max_b = mesh.bounds()
          rang = mesh.range()
          cm = mesh.centre()
          text_per_line = [
              "> {}".format(name_of_callable(mesh)),
              "> {} points".format(mesh.n_points),
              "> Bounds: [{0:.1f}-{1:.1f}]X, [{2:.1f}-{3:.1f}]Y, "
              "[{4:.1f}-{5:.1f}]Z".format(
                  min_b[0], max_b[0], min_b[1], max_b[1], min_b[2], max_b[2]),
              "> Range: {0:.1f}X, {1:.1f}Y, {2:.1f}Z".format(rang[0], rang[1],
                                                             rang[2]),
              "> Centre of mass: ({0:.1f}X, {1:.1f}Y, {2:.1f}Z)".format(
                  cm[0], cm[1], cm[2]),
              "> Norm: {0:.2f}".format(mesh.norm())]
          if custom_info_callback is not None:
              # iterate over the list of messages returned by the callback
              # function and append them in the text_per_line.
              for msg in custom_info_callback(mesh):
                  text_per_line.append('> {}'.format(msg))
          info_wid.set_widget_state(text_per_line=text_per_line)
  
      # Create widgets
      mesh_options_wid = Mesh3DOptionsWidget(
          textured=(isinstance(meshes[0], ColouredTriMesh) or
                    isinstance(meshes[0], TexturedTriMesh)),
          render_function=render_function)
      info_wid = TextPrintWidget(text_per_line=[''])
      save_figure_wid = SaveMayaviFigureOptionsWidget()
  
      # Group widgets
      if n_meshes > 1:
          # Define function that updates options' widgets state
          def update_widgets(change):
              i = change['new']
  
              # Update shape options
              mesh_options_wid.set_widget_state(
                  textured=(isinstance(meshes[i], ColouredTriMesh) or
                            isinstance(meshes[i], TexturedTriMesh)),
                  allow_callback=True)
  
          # selection slider
          index = {'min': 0, 'max': n_meshes-1, 'step': 1, 'index': 0}
          mesh_number_wid = AnimationOptionsWidget(
              index, render_function=update_widgets, index_style=browser_style,
              interval=0.2, description='Mesh', loop_enabled=True,
              continuous_update=False)
  
          # Header widget
          logo_wid = LogoWidget(style=main_style)
          logo_wid.layout.margin = '0px 10px 0px 0px'
          header_wid = ipywidgets.HBox([logo_wid, mesh_number_wid])
          header_wid.layout.align_items = 'center'
          header_wid.layout.margin = '0px 0px 10px 0px'
      else:
          # Header widget
          header_wid = LogoWidget(style=main_style)
          header_wid.layout.margin = '0px 10px 0px 0px'
      options_box = ipywidgets.Tab([info_wid, mesh_options_wid, save_figure_wid])
      tab_titles = ['Info', 'Mesh', 'Export']
      for (k, tl) in enumerate(tab_titles):
          options_box.set_title(k, tl)
      if n_meshes > 1:
          wid = ipywidgets.VBox([header_wid, options_box])
      else:
          wid = ipywidgets.HBox([header_wid, options_box])
  
      # Set widget's style
      wid.box_style = main_style
      wid.layout.border = '2px solid ' + map_styles_to_hex_colours(main_style)
  
      # Display final widget
      final_box = ipywidgets.Box([wid])
      final_box.layout.display = 'flex'
      ipydisplay.display(final_box)
  
      # Trigger initial visualization
      render_function({})
      with out:
          print_dynamic('')
  
  
  def visualize_images(images, figure_size=(7, 7), browser_style='buttons',
                       custom_info_callback=None):
      r"""
      Widget that allows browsing through a `list` of `menpo.image.Image` (or
      subclass) objects. The images can have a combination of different
      attributes, e.g. masked or not, landmarked or not, without multiple
      landmark groups and labels etc.
  
      Parameters
      ----------
      images : `list` of `menpo.image.Image` or subclass
          The `list` of images to be visualized.
      figure_size : (`int`, `int`), optional
          The initial size of the rendered figure.
      browser_style : ``{'buttons', 'slider'}``, optional
          It defines whether the selector of the objects will have the form of
          plus/minus buttons or a slider.
      custom_info_callback: `function` or ``None``, optional
          If not None, it should be a function that accepts an image and returns
          a list of custom messages to be printed per image. Each custom message
          will be printed in a separate line.
      """
      # Ensure that the code is being run inside a Jupyter kernel!
      from .utils import verify_ipython_and_kernel
      verify_ipython_and_kernel()
      print('Initializing...')
  
      # Make sure that images is a list even with one member
      if not isinstance(images, Sized):
          images = [images]
  
      # Get the number of images
      n_images = len(images)
  
      # Define the styling options
      main_style = 'info'
  
      # Define render function
      def render_function(change):
          # Clear current figure, but wait until the generation of the new data
          # that will be rendered
          ipydisplay.clear_output(wait=True)
  
          # get selected index and selected group
          i = image_number_wid.selected_values if n_images > 1 else 0
          g = landmark_options_wid.selected_values['landmarks']['group']
  
          # check if image is masked
          image_is_masked = isinstance(images[i], MaskedImage)
  
          # Create options dictionary
          options = dict()
          options.update(landmark_options_wid.selected_values['lines'])
          options.update(landmark_options_wid.selected_values['markers'])
          options.update(
              renderer_options_wid.selected_values['numbering_matplotlib'])
          options.update(renderer_options_wid.selected_values['axes'])
          options.update(renderer_options_wid.selected_values['legend'])
          options.update(image_options_wid.selected_values)
          options.update(landmark_options_wid.selected_values['landmarks'])
  
          # Correct options based on the type of the shape
          if (images[i].has_landmarks and
                  hasattr(images[i].landmarks[g], 'labels')):
              # If the shape is a LabelledPointUndirectedGraph ...
              # ...correct colours
              line_colour = []
              marker_face_colour = []
              marker_edge_colour = []
              for lbl in options['with_labels']:
                  id = images[i].landmarks[g].labels.index(lbl)
                  line_colour.append(options['line_colour'][id])
                  marker_face_colour.append(options['marker_face_colour'][id])
                  marker_edge_colour.append(options['marker_edge_colour'][id])
              options['line_colour'] = line_colour
              options['marker_face_colour'] = marker_face_colour
              options['marker_edge_colour'] = marker_edge_colour
          else:
              # If shape is PointCloud, TriMesh or PointGraph
              # ...correct colours
              options['line_colour'] = options['line_colour'][0]
              options['marker_face_colour'] = options['marker_face_colour'][0]
              options['marker_edge_colour'] = options['marker_edge_colour'][0]
  
          # Get figure size
          new_figure_size = (
              renderer_options_wid.selected_values['zoom_one'] *
              figure_size[0],
              renderer_options_wid.selected_values['zoom_one'] *
              figure_size[1])
  
          # Render shape with selected options
          save_figure_wid.renderer = render_image(
              image=images[i], renderer=save_figure_wid.renderer,
              image_is_masked=image_is_masked, figure_size=new_figure_size,
              **options)
  
          # Update info
          update_info(images[i], image_is_masked, g,
                      custom_info_callback=custom_info_callback)
  
      # Define function that updates the info text
      def update_info(img, image_is_masked, group, custom_info_callback=None):
          # Prepare masked (or non-masked) string
          masked_str = 'Masked Image' if image_is_masked else 'Image'
          # Get image path, if available
          path_str = img.path if hasattr(img, 'path') else 'No path available'
          # Create text lines
          text_per_line = [
              "> {} of size {} with {} channel{}".format(
                  masked_str, img._str_shape(), img.n_channels,
                  's' * (img.n_channels > 1)),
              "> Path: '{}'".format(path_str)]
          if image_is_masked:
              text_per_line.append(
                  "> {} masked pixels (attached mask {:.1%} true)".format(
                      img.n_true_pixels(), img.mask.proportion_true()))
          text_per_line.append("> min={:.3f}, max={:.3f}".format(
              img.pixels.min(), img.pixels.max()))
          if img.has_landmarks:
              text_per_line.append("> {} landmark points".format(
                  img.landmarks[group].n_points))
          if custom_info_callback is not None:
              # iterate over the list of messages returned by the callback
              # function and append them in the text_per_line.
              for msg in custom_info_callback(img):
                  text_per_line.append('> {}'.format(msg))
          info_wid.set_widget_state(text_per_line=text_per_line)
  
      # Create widgets
      groups_keys, labels_keys = extract_groups_labels_from_image(images[0])
      first_label = labels_keys[0] if labels_keys else None
      image_options_wid = ImageOptionsWidget(
          n_channels=images[0].n_channels,
          image_is_masked=isinstance(images[0], MaskedImage),
          render_function=render_function)
      landmark_options_wid = LandmarkOptionsWidget(
          group_keys=groups_keys, labels_keys=labels_keys,
          type='2D', render_function=render_function)
      renderer_options_wid = RendererOptionsWidget(
          options_tabs=['zoom_one', 'axes', 'numbering_matplotlib', 'legend'],
          labels=first_label, axes_x_limits=None, axes_y_limits=None,
          render_function=render_function)
      info_wid = TextPrintWidget(text_per_line=[''])
      save_figure_wid = SaveMatplotlibFigureOptionsWidget()
  
      # Group widgets
      if n_images > 1:
          # Define function that updates options' widgets state
          def update_widgets(change):
              # Get new groups and labels, then update landmark options
              i = image_number_wid.selected_values
              g_keys, l_keys = extract_groups_labels_from_image(images[i])
  
              # Update landmarks options
              landmark_options_wid.set_widget_state(
                  group_keys=g_keys, labels_keys=l_keys, allow_callback=False)
  
              # Update channels options
              image_options_wid.set_widget_state(
                  n_channels=images[i].n_channels,
                  image_is_masked=isinstance(images[i], MaskedImage),
                  allow_callback=True)
  
          # Image selection slider
          index = {'min': 0, 'max': n_images-1, 'step': 1, 'index': 0}
          image_number_wid = AnimationOptionsWidget(
              index, render_function=update_widgets, index_style=browser_style,
              interval=0.2, description='Image', loop_enabled=True,
              continuous_update=False)
  
          # Header widget
          logo_wid = LogoWidget(style=main_style)
          logo_wid.layout.margin = '0px 10px 0px 0px'
          header_wid = ipywidgets.HBox([logo_wid, image_number_wid])
          header_wid.layout.align_items = 'center'
          header_wid.layout.margin = '0px 0px 10px 0px'
      else:
          # Header widget
          header_wid = LogoWidget(style=main_style)
          header_wid.layout.margin = '0px 10px 0px 0px'
      options_box = ipywidgets.Tab(
          children=[info_wid, image_options_wid, landmark_options_wid,
                    renderer_options_wid, save_figure_wid])
      tab_titles = ['Info', 'Image', 'Landmarks', 'Renderer', 'Export']
      for (k, tl) in enumerate(tab_titles):
          options_box.set_title(k, tl)
      if n_images > 1:
          wid = ipywidgets.VBox([header_wid, options_box])
      else:
          wid = ipywidgets.HBox([header_wid, options_box])
  
      # Set widget's style
      wid.box_style = main_style
      wid.layout.border = '2px solid ' + map_styles_to_hex_colours(main_style)
  
      # Display final widget
      final_box = ipywidgets.Box([wid])
      final_box.layout.display = 'flex'
      ipydisplay.display(final_box)
  
      # Trigger initial visualization
      render_function({})
  
  
  def visualize_patches(patches, patch_centers, figure_size=(7, 7),
                        browser_style='buttons', custom_info_callback=None):
      r"""
      Widget that allows browsing through a `list` of patch-based images.
  
      The patches argument can have any of the two formats that are returned from
      the `extract_patches()` and `extract_patches_around_landmarks()` methods
      of `menpo.image.Image`. Specifically it can be:
  
          1. ``(n_center, n_offset, self.n_channels, patch_shape)`` `ndarray`
          2. `list` of ``n_center * n_offset`` `menpo.image.Image` objects
  
      The patches can have a combination of different attributes, e.g. number of
      centers, number of offsets, number of channels etc.
  
      Parameters
      ----------
      patches : `list`
          The `list` of patch-based images to be visualized. It can consist of
          objects with any of the two formats that are returned from the
          `extract_patches()` and `extract_patches_around_landmarks()` methods.
          Specifically, it can either be an
          ``(n_center, n_offset, self.n_channels, patch_shape)`` `ndarray` or a
          `list` of ``n_center * n_offset`` `menpo.image.Image` objects.
      patch_centers : `list` of `menpo.shape.PointCloud`
          The centers to set the patches around. If the `list` has only one
          `menpo.shape.PointCloud` then this will be used for all patches members.
          Otherwise, it needs to have the same length as patches.
      figure_size : (`int`, `int`), optional
          The initial size of the rendered figure.
      browser_style : ``{'buttons', 'slider'}``, optional
          It defines whether the selector of the objects will have the form of
          plus/minus buttons or a slider.
      custom_info_callback: `function` or ``None``, optional
          If not None, it should be a function that accepts an image and returns
          a list of custom messages to be printed per image. Each custom message
          will be printed in a separate line.
      """
      # Ensure that the code is being run inside a Jupyter kernel!
      from .utils import verify_ipython_and_kernel
      verify_ipython_and_kernel()
      print('Initializing...')
  
      # Make sure that patches is a list even with one member
      if (isinstance(patches, list) and isinstance(patches[0], Image)) or \
              not isinstance(patches, list):
          patches = [patches]
  
      # Make sure that patch_centers is a list even with one shape
      if not isinstance(patch_centers, list):
          patch_centers = [patch_centers] * len(patches)
      elif isinstance(patch_centers, list) and len(patch_centers) == 1:
          patch_centers *= len(patches)
  
      # Make sure all patch-based images are in the single array format
      for i in range(len(patches)):
          if isinstance(patches[i], list):
              patches[i] = _convert_patches_list_to_single_array(
                  patches[i], patch_centers[i].n_points)
  
      # Get the number of patch_based images
      n_patches = len(patches)
  
      # Define the styling options
      main_style = 'info'
  
      # Define render function
      def render_function(change):
          # Clear current figure, but wait until the generation of the new data
          # that will be rendered
          ipydisplay.clear_output(wait=True)
  
          # get selected index
          i = image_number_wid.selected_values if n_patches > 1 else 0
  
          # Create options dictionary
          options = dict()
          options.update(shape_options_wid.selected_values['lines'])
          options.update(shape_options_wid.selected_values['markers'])
          options.update(
              renderer_options_wid.selected_values['numbering_matplotlib'])
          options.update(renderer_options_wid.selected_values['axes'])
          image_options = dict(image_options_wid.selected_values)
          del image_options['masked_enabled']
          options.update(image_options)
          options.update(patch_options_wid.selected_values)
          options['line_colour'] = options['line_colour'][0]
          options['marker_face_colour'] = options['marker_face_colour'][0]
          options['marker_edge_colour'] = options['marker_edge_colour'][0]
  
          # Get figure size
          new_figure_size = (
              renderer_options_wid.selected_values['zoom_one'] * figure_size[0],
              renderer_options_wid.selected_values['zoom_one'] * figure_size[1])
  
          # Render image with selected options
          save_figure_wid.renderer = render_patches(
              patches=patches[i], patch_centers=patch_centers[i],
              renderer=save_figure_wid.renderer, figure_size=new_figure_size,
              **options)
  
          # update info text widget
          update_info(patches[i], custom_info_callback=custom_info_callback)
  
      # Define function that updates the info text
      def update_info(ptchs, custom_info_callback=None):
          text_per_line = [
              "> Patch-Based Image with {} patche{} and {} offset{}.".format(
                  ptchs.shape[0], 's' * (ptchs.shape[0] > 1), ptchs.shape[1],
                                  's' * (ptchs.shape[1] > 1)),
              "> Each patch has size {}H x {}W with {} channel{}.".format(
                  ptchs.shape[3], ptchs.shape[4], ptchs.shape[2],
                  's' * (ptchs.shape[2] > 1)),
              "> min={:.3f}, max={:.3f}".format(ptchs.min(), ptchs.max())]
          if custom_info_callback is not None:
              # iterate over the list of messages returned by the callback
              # function and append them in the text_per_line.
              for msg in custom_info_callback(ptchs):
                  text_per_line.append('> {}'.format(msg))
          info_wid.set_widget_state(text_per_line=text_per_line)
  
      # Create widgets
      shape_options_wid = Shape2DOptionsWidget(
          labels=None, render_function=None)
      shape_options_wid.line_options_wid.render_lines_switch.button_wid.value = False
      shape_options_wid.add_render_function(render_function)
      patch_options_wid = PatchOptionsWidget(
          n_patches=patches[0].shape[0], n_offsets=patches[0].shape[1],
          render_function=render_function)
      image_options_wid = ImageOptionsWidget(
          n_channels=patches[0].shape[2], image_is_masked=False,
          render_function=None)
      image_options_wid.interpolation_checkbox.button_wid.value = False
      image_options_wid.add_render_function(render_function)
      renderer_options_wid = RendererOptionsWidget(
          options_tabs=['zoom_one', 'axes', 'numbering_matplotlib'], labels=None,
          axes_x_limits=None, axes_y_limits=None,
          render_function=render_function)
      info_wid = TextPrintWidget(text_per_line=[''])
      save_figure_wid = SaveMatplotlibFigureOptionsWidget()
  
      # Group widgets
      if n_patches > 1:
          # Define function that updates options' widgets state
          def update_widgets(change):
              # Selected object
              i = image_number_wid.selected_values
  
              # Update patch options
              patch_options_wid.set_widget_state(
                  n_patches=patches[i].shape[0], n_offsets=patches[i].shape[1],
                  allow_callback=False)
  
              # Update channels options
              image_options_wid.set_widget_state(
                  n_channels=patches[i].shape[2], image_is_masked=False,
                  allow_callback=True)
  
          # Image selection slider
          index = {'min': 0, 'max': n_patches-1, 'step': 1, 'index': 0}
          image_number_wid = AnimationOptionsWidget(
              index, render_function=update_widgets, index_style=browser_style,
              interval=0.2, description='Image', loop_enabled=True,
              continuous_update=False)
  
          # Header widget
          logo_wid = LogoWidget(style=main_style)
          logo_wid.layout.margin = '0px 10px 0px 0px'
          header_wid = ipywidgets.HBox([logo_wid, image_number_wid])
          header_wid.layout.align_items = 'center'
          header_wid.layout.margin = '0px 0px 10px 0px'
      else:
          # Header widget
          header_wid = LogoWidget(style=main_style)
          header_wid.layout.margin = '0px 10px 0px 0px'
      options_box = ipywidgets.Tab(
          children=[info_wid, patch_options_wid, image_options_wid,
                    shape_options_wid, renderer_options_wid, save_figure_wid])
      tab_titles = ['Info', 'Patches', 'Image', 'Shape', 'Renderer', 'Export']
      for (k, tl) in enumerate(tab_titles):
          options_box.set_title(k, tl)
      if n_patches > 1:
          wid = ipywidgets.VBox([header_wid, options_box])
      else:
          wid = ipywidgets.HBox([header_wid, options_box])
  
      # Set widget's style
      wid.box_style = main_style
      wid.layout.border = '2px solid ' + map_styles_to_hex_colours(main_style)
  
      # Display final widget
      final_box = ipywidgets.Box([wid])
      final_box.layout.display = 'flex'
      ipydisplay.display(final_box)
  
      # Trigger initial visualization
      render_function({})
  
  
  def plot_graph(x_axis, y_axis, legend_entries=None, figure_size=(9, 5)):
      r"""
      Widget that allows plotting various curves in a graph.
  
      The widget has options tabs regarding the graph and the renderer (lines,
      markers, legend, figure, axes, grid) and saving the figure to file.
  
      Parameters
      ----------
      x_axis : `list` of `float`
          The values of the horizontal axis. Note that these values are common for
          all the curves.
      y_axis : `list` of `lists` of `float`
          A `list` that stores a `list` of values to be plotted for each curve.
      legend_entries : `list` or `str` or ``None``, optional
          The `list` of names that will appear on the legend for each curve. If
          ``None``, then the names format is ``curve {}.format(i)``.
      figure_size : (`int`, `int`), optional
          The initial size of the rendered figure.
      """
      # Ensure that the code is being run inside a Jupyter kernel!
      from .utils import verify_ipython_and_kernel
      verify_ipython_and_kernel()
      from menpo.visualize import plot_curve
      print('Initializing...')
  
      # Get number of curves to be plotted
      n_curves = len(y_axis)
  
      # Define the styling options
      main_style = 'danger'
  
      # Parse options
      if legend_entries is None:
          legend_entries = ["curve {}".format(i) for i in range(n_curves)]
  
      # Define render function
      def render_function(change):
          # Clear current figure, but wait until the generation of the new data
          # that will be rendered
          ipydisplay.clear_output(wait=True)
  
          # plot with selected options
          opts = plot_wid.selected_values.copy()
          new_figure_size = (
              plot_wid.selected_values['zoom'][0] * figure_size[0],
              plot_wid.selected_values['zoom'][1] * figure_size[1])
          del opts['zoom']
          save_figure_wid.renderer = plot_curve(
              x_axis=x_axis, y_axis=y_axis, figure_size=new_figure_size,
              figure_id=save_figure_wid.renderer.figure_id, new_figure=False,
              **opts)
  
          # show plot
          save_figure_wid.renderer.force_draw()
  
      # Create widgets
      plot_wid = PlotMatplotlibOptionsWidget(
          legend_entries=legend_entries, render_function=render_function)
      save_figure_wid = SaveMatplotlibFigureOptionsWidget()
  
      # Group widgets
      logo = LogoWidget(style=main_style)
      logo.layout.margin = '0px 10px 0px 0px'
      tmp_children = list(plot_wid.tab_box.children)
      tmp_children.append(save_figure_wid)
      plot_wid.tab_box.children = tmp_children
      plot_wid.tab_box.set_title(0, 'Labels')
      plot_wid.tab_box.set_title(1, 'Style')
      plot_wid.tab_box.set_title(2, 'Legend')
      plot_wid.tab_box.set_title(3, 'Axes')
      plot_wid.tab_box.set_title(4, 'Zoom')
      plot_wid.tab_box.set_title(5, 'Grid')
      plot_wid.tab_box.set_title(6, 'Export')
  
      # Display final widget
      wid = ipywidgets.HBox([logo, plot_wid])
      wid.box_style = main_style
      wid.layout.border = '2px solid' + map_styles_to_hex_colours(main_style)
      plot_wid.container.border = '0px'
      final_box = ipywidgets.Box([wid])
      final_box.layout.display = 'flex'
      ipydisplay.display(final_box)
  
      # Trigger initial visualization
      render_function({})
  
  
  def save_matplotlib_figure(renderer):
      r"""
      Widget that allows to save a figure, which was generated with Matplotlib,
      to file.
  
      Parameters
      ----------
      renderer : `menpo.visualize.viewmatplotlib.MatplotlibRenderer`
          The Matplotlib renderer object.
      """
      # Ensure that the code is being run inside a Jupyter kernel!
      from .utils import verify_ipython_and_kernel
      verify_ipython_and_kernel()
      # Create sub-widgets
      logo_wid = LogoWidget()
      logo_wid.layout.margin = '0px 10px 0px 0px'
      save_figure_wid = SaveMatplotlibFigureOptionsWidget(renderer,
                                                          style='warning')
      wid = ipywidgets.HBox([logo_wid, save_figure_wid])
  
      # Display widget
      ipydisplay.display(wid)
  
  
  def save_mayavi_figure(renderer):
      r"""
      Widget that allows to save a figure, which was generated with Mayavi,
      to file.
  
      Parameters
      ----------
      renderer : `menpo3d.visualize.viewmayavi.MayaviRenderer`
          The Mayavi renderer object.
      """
      # Ensure that the code is being run inside a Jupyter kernel!
      from .utils import verify_ipython_and_kernel
      verify_ipython_and_kernel()
      # Create sub-widgets
      logo_wid = LogoWidget()
      logo_wid.layout.margin = '0px 10px 0px 0px'
      save_figure_wid = SaveMayaviFigureOptionsWidget(renderer,
                                                      style='warning')
      wid = ipywidgets.HBox([logo_wid, save_figure_wid])
  
      # Display widget
      ipydisplay.display(wid)
  
  
  def visualize_shape_model_2d(shape_model, n_parameters=5, mode='multiple',
                               parameters_bounds=(-3.0, 3.0), figure_size=(7, 7)):
      r"""
      Widget that allows the dynamic visualization of a multi-scale linear
      statistical 2D shape model.
  
      Parameters
      ----------
      shape_model : `list` of `menpo.shape.PCAModel` or `subclass`
          The multi-scale shape model to be visualized. Note that each level can
          have different number of components.
      n_parameters : `int` or `list` of `int` or ``None``, optional
          The number of principal components to be used for the parameters
          sliders. If `int`, then the number of sliders per level is the minimum
          between `n_parameters` and the number of active components per level.
          If `list` of `int`, then a number of sliders is defined per level.
          If ``None``, all the active components per level will have a slider.
      mode : ``{'single', 'multiple'}``, optional
          If ``'single'``, then only a single slider is constructed along with a
          drop down menu. If ``'multiple'``, then a slider is constructed for each
          parameter.
      parameters_bounds : (`float`, `float`), optional
          The minimum and maximum bounds, in std units, for the sliders.
      figure_size : (`int`, `int`), optional
          The size of the plotted figures.
      """
      # Ensure that the code is being run inside a Jupyter kernel!
      from .utils import verify_ipython_and_kernel
      verify_ipython_and_kernel()
      from menpo.visualize.viewmatplotlib import (_set_axes_options,
                                                  _parse_axes_limits)
      out = ipywidgets.Output()
      ipydisplay.display(out)
      with out:
          ipydisplay.clear_output(wait=True)
          print('Initializing...')
  
      # Make sure that shape_model is a list even with one member
      if not isinstance(shape_model, list):
          shape_model = [shape_model]
  
      # Get the number of levels (i.e. number of shape models)
      n_levels = len(shape_model)
  
      # Define the styling options
      main_style = 'warning'
  
      # Get the maximum number of components per level
      max_n_params = [sp.n_active_components for sp in shape_model]
  
      # Check the given number of parameters (the returned n_parameters is a list
      # of len n_scales)
      n_parameters = check_n_parameters(n_parameters, n_levels, max_n_params)
  
      # Define render function
      def render_function(change):
          # Clear current figure, but wait until the generation of the new data
          # that will be rendered
          with out:
              ipydisplay.clear_output(wait=True)
  
          # Get selected level
          level = 0
          if n_levels > 1:
              level = level_wid.value
  
          # Compute weights
          parameters = model_parameters_wid.selected_values
          weights = (parameters *
                     shape_model[level].eigenvalues[:len(parameters)] ** 0.5)
  
          # Get the mean
          mean = shape_model[level].mean()
  
          # Create options dictionary
          options = dict()
          options.update(shape_options_wid.selected_values['lines'])
          options.update(shape_options_wid.selected_values['markers'])
          options['image_view'] = shape_options_wid.selected_values['image_view']
          options.update(
              renderer_options_wid.selected_values['numbering_matplotlib'])
          options.update(renderer_options_wid.selected_values['axes'])
  
          # Correct options based on the type of the shape
          if hasattr(mean, 'labels'):
              # If the shape is a LabelledPointUndirectedGraph ...
              # ...use the legend options
              options.update(renderer_options_wid.selected_values['legend'])
              # ...use with_labels
              options['with_labels'] = \
                  shape_options_wid.selected_values['with_labels']
              # ...correct colours
              line_colour = []
              marker_face_colour = []
              marker_edge_colour = []
              for lbl in options['with_labels']:
                  idx = mean.labels.index(lbl)
                  line_colour.append(options['line_colour'][idx])
                  marker_face_colour.append(options['marker_face_colour'][idx])
                  marker_edge_colour.append(options['marker_edge_colour'][idx])
              options['line_colour'] = line_colour
              options['marker_face_colour'] = marker_face_colour
              options['marker_edge_colour'] = marker_edge_colour
          else:
              # If shape is PointCloud, TriMesh or PointGraph
              # ...correct colours
              options['line_colour'] = options['line_colour'][0]
              options['marker_face_colour'] = options['marker_face_colour'][0]
              options['marker_edge_colour'] = options['marker_edge_colour'][0]
  
          # Get figure size
          new_figure_size = (
              renderer_options_wid.selected_values['zoom_one'] * figure_size[0],
              renderer_options_wid.selected_values['zoom_one'] * figure_size[1])
  
          # Render with selected options
          if mode_wid.value == 1:
              # Deformation mode
              # Compute instance
              instance = shape_model[level].instance(weights)
  
              # Render mean shape
              if mean_wid.selected_values:
                  save_figure_wid.renderer = mean.view(
                      figure_id=save_figure_wid.renderer.figure_id,
                      new_figure=False, figure_size=None,
                      image_view=options['image_view'],
                      render_lines=options['render_lines'],
                      line_colour='yellow', line_style=options['line_style'],
                      line_width=options['line_width'],
                      render_markers=options['render_markers'],
                      marker_style=options['marker_style'],
                      marker_size=options['marker_size'],
                      marker_face_colour='yellow', marker_edge_colour='yellow',
                      marker_edge_width=options['marker_edge_width'])
  
              # Render instance
              save_figure_wid.renderer = instance.view(
                  figure_id=save_figure_wid.renderer.figure_id, new_figure=False,
                  figure_size=new_figure_size, **options)
  
              # Get instance range
              instance_range = instance.range()
          else:
              # Vectors mode
              # Compute instance
              instance_lower = shape_model[level].instance([-p for p in weights])
              instance_upper = shape_model[level].instance(weights)
  
              # Render mean shape
              save_figure_wid.renderer = mean.view(
                  figure_id=save_figure_wid.renderer.figure_id, new_figure=False,
                  figure_size=new_figure_size, **options)
  
              # Render vectors
              ax = plt.gca()
              x_min = np.Inf
              y_min = np.Inf
              x_max = -np.Inf
              y_max = -np.Inf
              for p in range(mean.n_points):
                  xm = mean.points[p, 0]
                  ym = mean.points[p, 1]
                  xl = instance_lower.points[p, 0]
                  yl = instance_lower.points[p, 1]
                  xu = instance_upper.points[p, 0]
                  yu = instance_upper.points[p, 1]
                  if options['image_view']:
                      # image mode
                      lines = [[(ym, xm), (yl, xl)], [(ym, xm), (yu, xu)]]
                  else:
                      # point cloud mode
                      lines = [[(xm, ym), (xl, yl)], [(xm, ym), (xu, yu)]]
                  lc = mc.LineCollection(lines, colors=('g', 'b'),
                                         linestyles='solid', linewidths=2)
                  # update min, max
                  y_min = np.min([y_min, xl, xu])
                  y_max = np.max([y_max, xl, xu])
                  x_min = np.min([x_min, yl, yu])
                  x_max = np.max([x_max, yl, yu])
  
                  # add collection
                  ax.add_collection(lc)
  
              # parse axes limits
              axes_x_limits, axes_y_limits = _parse_axes_limits(
                      x_min, x_max, y_min, y_max, options['axes_x_limits'],
                      options['axes_y_limits'])
              _set_axes_options(
                  ax, render_axes=options['render_axes'],
                  inverted_y_axis=options['image_view'],
                  axes_font_name=options['axes_font_name'],
                  axes_font_size=options['axes_font_size'],
                  axes_font_style=options['axes_font_style'],
                  axes_font_weight=options['axes_font_weight'],
                  axes_x_limits=axes_x_limits, axes_y_limits=axes_y_limits,
                  axes_x_ticks=options['axes_x_ticks'],
                  axes_y_ticks=options['axes_y_ticks'])
  
              # Get instance range
              instance_range = mean.range()
  
          # Force rendering
          save_figure_wid.renderer.force_draw()
  
          # Update info
          update_info(level, instance_range)
  
      # Define function that updates the info text
      def update_info(level, instance_range):
          text_per_line = [
              "> Level {} out of {}".format(level + 1, n_levels),
              "> {} components in total".format(shape_model[level].n_components),
              "> {} active components".format(
                  shape_model[level].n_active_components),
              "> {:.1f}% variance kept".format(
                  shape_model[level].variance_ratio() * 100),
              "> Instance range: {:.1f} x {:.1f}".format(instance_range[0],
                                                         instance_range[1]),
              "> {} landmark points, {} features".format(
                  shape_model[level].mean().n_points,
                  shape_model[level].n_features)]
          info_wid.set_widget_state(text_per_line=text_per_line)
  
      # Plot variance function
      def plot_variance(name):
          # Clear current figure, but wait until the generation of the new data
          # that will be rendered
          ipydisplay.clear_output(wait=True)
  
          # Get selected level
          level = level_wid.value if n_levels > 1 else 0
  
          # Render
          new_figure_size = (
              renderer_options_wid.selected_values['zoom_one'] * 10,
              renderer_options_wid.selected_values['zoom_one'] * 3)
          plt.subplot(121)
          save_figure_wid.renderer = shape_model[level].plot_eigenvalues_ratio(
              figure_id=save_figure_wid.renderer.figure_id, new_figure=False)
          plt.subplot(122)
          save_figure_wid.renderer = \
              shape_model[level].plot_eigenvalues_cumulative_ratio(
                  figure_id=save_figure_wid.renderer.figure_id, new_figure=False,
                  figure_size=new_figure_size)
          save_figure_wid.renderer.force_draw()
  
      # Create widgets
      mode_dict = OrderedDict()
      mode_dict['Deformation'] = 1
      mode_dict['Vectors'] = 2
      mode_wid = ipywidgets.RadioButtons(
          options=mode_dict, description='Mode', value=1,
          layout=ipywidgets.Layout(width='6cm'))
      mode_wid.observe(render_function, names='value', type='change')
      mean_wid = SwitchWidget(
          selected_value=False, description='Render mean shape',
          description_location='right', switch_type='checkbox')
      mean_wid.observe(render_function, names='selected_values', type='change')
  
      # Function that controls mean shape checkbox visibility
      def mean_visible(change):
          if change['new'] == 1:
              mean_wid.button_wid.disabled = False
          else:
              mean_wid.button_wid.disabled = True
              mean_wid.set_widget_state(False, allow_callback=False)
      mode_wid.observe(mean_visible, names='value', type='change')
      model_parameters_wid = LinearModelParametersWidget(
          n_parameters[0], render_function, params_str='Parameter ',
          mode=mode, params_bounds=parameters_bounds, params_step=0.1,
          plot_variance_visible=True, plot_variance_function=plot_variance,
          animation_step=0.5, interval=0., loop_enabled=True,
          continuous_update=False)
      labels = None
      if hasattr(shape_model[0].mean(), 'labels'):
          labels = shape_model[0].mean().labels
      shape_options_wid = Shape2DOptionsWidget(
          labels=labels, render_function=render_function)
      renderer_options_wid = RendererOptionsWidget(
          options_tabs=['zoom_one', 'axes', 'numbering_matplotlib', 'legend'],
          labels=None,  axes_x_limits=0.1, axes_y_limits=0.1,
          render_function=render_function)
      info_wid = TextPrintWidget(text_per_line=[''])
      save_figure_wid = SaveMatplotlibFigureOptionsWidget()
  
      # Group widgets
      if n_levels > 1:
          # Define function that updates options' widgets state
          def update_widgets(change):
              model_parameters_wid.set_widget_state(
                  n_parameters=n_parameters[change['new']],
                  params_str='Parameter ', allow_callback=True)
  
          # Create pyramid radiobuttons
          radio_str = OrderedDict()
          for l in range(n_levels):
              if l == 0:
                  radio_str["Level {} (low)".format(l)] = l
              elif l == n_levels - 1:
                  radio_str["Level {} (high)".format(l)] = l
              else:
                  radio_str["Level {}".format(l)] = l
          level_wid = ipywidgets.RadioButtons(
              options=radio_str, description='Pyramid', value=n_levels-1,
              layout=ipywidgets.Layout(width='6cm'))
          level_wid.observe(update_widgets, names='value', type='change')
          level_wid.observe(render_function, names='value', type='change')
          radio_children = [level_wid, mode_wid, mean_wid]
      else:
          radio_children = [mode_wid, mean_wid]
      radio_wids = ipywidgets.VBox(radio_children)
      tmp_wid = ipywidgets.HBox([radio_wids, model_parameters_wid])
      options_box = ipywidgets.Tab(
          children=[tmp_wid, shape_options_wid, renderer_options_wid, info_wid,
                    save_figure_wid])
      tab_titles = ['Model', 'Shape', 'Renderer', 'Info', 'Export']
      for (k, tl) in enumerate(tab_titles):
          options_box.set_title(k, tl)
      logo_wid = LogoWidget(style=main_style)
      logo_wid.layout.margin = '0px 10px 0px 0px'
      wid = ipywidgets.HBox([logo_wid, options_box])
  
      # Set widget's style
      wid.box_style = main_style
      wid.layout.border = '2px solid ' + map_styles_to_hex_colours(main_style)
  
      # Display final widget
      final_box = ipywidgets.Box([wid])
      final_box.layout.display = 'flex'
      ipydisplay.display(final_box)
  
      # Trigger initial visualization
      render_function({})
  
  
  def visualize_shape_model_3d(shape_model, n_parameters=5, mode='multiple',
                               parameters_bounds=(-15.0, 15.0)):
      r"""
      Widget that allows the dynamic visualization of a multi-scale linear
      statistical 3D shape model.
  
      Parameters
      ----------
      shape_model : `list` of `menpo.shape.PCAModel` or `subclass`
          The multi-scale shape model to be visualized. Note that each level can
          have different number of components.
      n_parameters : `int` or `list` of `int` or ``None``, optional
          The number of principal components to be used for the parameters
          sliders. If `int`, then the number of sliders per level is the minimum
          between `n_parameters` and the number of active components per level.
          If `list` of `int`, then a number of sliders is defined per level.
          If ``None``, all the active components per level will have a slider.
      mode : ``{'single', 'multiple'}``, optional
          If ``'single'``, then only a single slider is constructed along with a
          drop down menu. If ``'multiple'``, then a slider is constructed for each
          parameter.
      parameters_bounds : (`float`, `float`), optional
          The minimum and maximum bounds, in std units, for the sliders.
      """
      # Ensure that the code is being run inside a Jupyter kernel!
      from .utils import verify_ipython_and_kernel
      verify_ipython_and_kernel()
      out = ipywidgets.Output()
      ipydisplay.display(out)
      with out:
          ipydisplay.clear_output(wait=True)
          print('Initializing...')
  
      # Make sure that shape_model is a list even with one member
      if not isinstance(shape_model, list):
          shape_model = [shape_model]
  
      # Get the number of levels (i.e. number of shape models)
      n_levels = len(shape_model)
  
      # Check if the model is TriMesh or any other 3D shape class
      is_trimesh = isinstance(shape_model[0].template_instance, TriMesh)
  
      # Define the styling options
      main_style = 'warning'
  
      # Get the maximum number of components per level
      max_n_params = [sp.n_active_components for sp in shape_model]
  
      # Check the given number of parameters (the returned n_parameters is a list
      # of len n_scales)
      n_parameters = check_n_parameters(n_parameters, n_levels, max_n_params)
  
      # Define render function
      def render_function(change):
          # Clear current figure, but wait until the generation of the new data
          # that will be rendered
          save_figure_wid.renderer.clear_figure()
          with out:
              ipydisplay.clear_output(wait=True)
  
          # Get selected level
          level = 0
          if n_levels > 1:
              level = level_wid.value
  
          # Compute weights
          parameters = model_parameters_wid.selected_values
          weights = (parameters *
                     shape_model[level].eigenvalues[:len(parameters)] ** 0.5)
  
          # Compute instance
          instance = shape_model[level].instance(weights)
  
          # Create options dictionary
          options = dict()
          if is_trimesh:
              options.update(shape_options_wid.selected_values)
          else:
              options.update(shape_options_wid.selected_values['lines'])
              options.update(shape_options_wid.selected_values['markers'])
              options.update(
                  renderer_options_wid.selected_values['numbering_mayavi'])
              # Correct options based on the type of the shape
              if hasattr(instance, 'labels'):
                  # If the shape is a LabelledPointUndirectedGraph ...
                  # ...use with_labels
                  options['with_labels'] = \
                      shape_options_wid.selected_values['with_labels']
                  # ...correct colours
                  line_colour = []
                  marker_colour = []
                  for lbl in options['with_labels']:
                      idx = instance.labels.index(lbl)
                      line_colour.append(options['line_colour'][idx])
                      marker_colour.append(options['marker_colour'][idx])
                  options['line_colour'] = line_colour
                  options['marker_colour'] = marker_colour
              else:
                  # If shape is PointCloud, TriMesh or PointGraph
                  # ...correct colours
                  options['line_colour'] = options['line_colour'][0]
                  options['marker_colour'] = options['marker_colour'][0]
  
          # Update info
          update_info(level, instance.range())
  
          # Render instance
          save_figure_wid.renderer = instance.view(
              figure_id=save_figure_wid.renderer.figure_id, new_figure=False,
              **options)
  
          # Force rendering
          save_figure_wid.renderer.force_draw()
  
      # Define function that updates the info text
      def update_info(level, instance_range):
          text_per_line = [
              "> Level {} out of {}".format(level + 1, n_levels),
              "> {} components in total".format(shape_model[level].n_components),
              "> {} active components".format(
                  shape_model[level].n_active_components),
              "> {:.1f}% variance kept".format(
                  shape_model[level].variance_ratio() * 100),
              "> Instance range: {:.1f} x {:.1f}".format(instance_range[0],
                                                         instance_range[1]),
              "> {} points".format(
                  shape_model[level].mean().n_points)]
          info_wid.set_widget_state(text_per_line=text_per_line)
  
      # Plot variance function
      def plot_variance(name):
          # Clear current figure, but wait until the generation of the new data
          # that will be rendered
  
          # Get selected level
          level = level_wid.value if n_levels > 1 else 0
  
          # Render
          with out:
              ipydisplay.clear_output(wait=True)
              plt.subplot(121)
              shape_model[level].plot_eigenvalues_ratio()
              plt.subplot(122)
              shape_model[level].plot_eigenvalues_cumulative_ratio()
              plt.show()
  
      # Create widgets
      model_parameters_wid = LinearModelParametersWidget(
          n_parameters[0], render_function, params_str='Parameter ',
          mode=mode, params_bounds=parameters_bounds, params_step=0.1,
          plot_variance_visible=True, plot_variance_function=plot_variance,
          animation_step=0.5, interval=0., loop_enabled=True,
          continuous_update=False)
      if is_trimesh:
          shape_options_wid = Mesh3DOptionsWidget(textured=False,
                                                  render_function=render_function)
      else:
          labels = None
          if hasattr(shape_model[0].mean(), 'labels'):
              labels = shape_model[0].mean().labels
          shape_options_wid = Shape3DOptionsWidget(labels=labels,
                                                   render_function=render_function)
          renderer_options_wid = RendererOptionsWidget(
              options_tabs=['numbering_mayavi'], labels=None,
              render_function=render_function)
      info_wid = TextPrintWidget(text_per_line=[''])
      save_figure_wid = SaveMayaviFigureOptionsWidget()
  
      # Group widgets
      if n_levels > 1:
          # Define function that updates options' widgets state
          def update_widgets(change):
              model_parameters_wid.set_widget_state(
                  n_parameters=n_parameters[change['new']],
                  params_str='Parameter ', allow_callback=True)
  
          # Create pyramid radiobuttons
          radio_str = OrderedDict()
          for l in range(n_levels):
              if l == 0:
                  radio_str["Level {} (low)".format(l)] = l
              elif l == n_levels - 1:
                  radio_str["Level {} (high)".format(l)] = l
              else:
                  radio_str["Level {}".format(l)] = l
          level_wid = ipywidgets.RadioButtons(
              options=radio_str, description='Pyramid', value=n_levels-1,
              layout=ipywidgets.Layout(width='6cm'))
          level_wid.observe(update_widgets, names='value', type='change')
          level_wid.observe(render_function, names='value', type='change')
          tmp_wid = ipywidgets.HBox([level_wid, model_parameters_wid])
      else:
          tmp_wid = ipywidgets.HBox(children=[model_parameters_wid])
      if is_trimesh:
          options_box = ipywidgets.Tab(
              children=[tmp_wid, shape_options_wid, info_wid, save_figure_wid])
          tab_titles = ['Model', 'Mesh', 'Info', 'Export']
      else:
          options_box = ipywidgets.Tab(
              children=[tmp_wid, shape_options_wid, renderer_options_wid, info_wid,
                        save_figure_wid])
          tab_titles = ['Model', 'Shape', 'Renderer', 'Info', 'Export']
      for (k, tl) in enumerate(tab_titles):
          options_box.set_title(k, tl)
      logo_wid = LogoWidget(style=main_style)
      logo_wid.layout.margin = '0px 10px 0px 0px'
      wid = ipywidgets.HBox([logo_wid, options_box])
  
      # Set widget's style
      wid.box_style = main_style
      wid.layout.border = '2px solid ' + map_styles_to_hex_colours(main_style)
  
      # Display final widget
      final_box = ipywidgets.Box([wid])
      final_box.layout.display = 'flex'
      ipydisplay.display(final_box)
  
      # Trigger initial visualization
      render_function({})
      with out:
          print_dynamic('')
  
  
  def visualize_appearance_model(appearance_model, n_parameters=5,
                                 mode='multiple', parameters_bounds=(-3.0, 3.0),
                                 figure_size=(7, 7)):
      r"""
      Widget that allows the dynamic visualization of a multi-scale linear
      statistical appearance model.
  
      Parameters
      ----------
      appearance_model : `list` of `menpo.model.PCAModel` or subclass
          The multi-scale appearance model to be visualized. Note that each level
          can have different number of components.
      n_parameters : `int` or `list` of `int` or ``None``, optional
          The number of principal components to be used for the parameters
          sliders. If `int`, then the number of sliders per level is the minimum
          between `n_parameters` and the number of active components per level.
          If `list` of `int`, then a number of sliders is defined per level.
          If ``None``, all the active components per level will have a slider.
      mode : ``{'single', 'multiple'}``, optional
          If ``'single'``, then only a single slider is constructed along with a
          drop down menu. If ``'multiple'``, then a slider is constructed for each
          parameter.
      parameters_bounds : (`float`, `float`), optional
          The minimum and maximum bounds, in std units, for the sliders.
      figure_size : (`int`, `int`), optional
          The size of the plotted figures.
      """
      # Ensure that the code is being run inside a Jupyter kernel!
      from .utils import verify_ipython_and_kernel
      verify_ipython_and_kernel()
      print('Initializing...')
  
      # Make sure that appearance_model is a list even with one member
      if not isinstance(appearance_model, list):
          appearance_model = [appearance_model]
  
      # Get the number of levels (i.e. number of appearance models)
      n_levels = len(appearance_model)
  
      # Define the styling options
      main_style = 'success'
  
      # Get the maximum number of components per level
      max_n_params = [ap.n_active_components for ap in appearance_model]
  
      # Check the given number of parameters (the returned n_parameters is a list
      # of len n_scales)
      n_parameters = check_n_parameters(n_parameters, n_levels, max_n_params)
  
      # Define render function
      def render_function(change):
          # Clear current figure, but wait until the generation of the new data
          # that will be rendered
          ipydisplay.clear_output(wait=True)
  
          # Get selected level
          level = level_wid.value if n_levels > 1 else 0
  
          # Compute weights and instance
          parameters = model_parameters_wid.selected_values
          weights = (parameters *
                     appearance_model[level].eigenvalues[:len(parameters)] ** 0.5)
          instance = appearance_model[level].instance(weights)
          image_is_masked = isinstance(instance, MaskedImage)
          g = landmark_options_wid.selected_values['landmarks']['group']
  
          # Create options dictionary
          options = dict()
          options.update(landmark_options_wid.selected_values['lines'])
          options.update(landmark_options_wid.selected_values['markers'])
          options.update(
              renderer_options_wid.selected_values['numbering_matplotlib'])
          options.update(renderer_options_wid.selected_values['axes'])
          options.update(renderer_options_wid.selected_values['legend'])
          options.update(image_options_wid.selected_values)
          options.update(landmark_options_wid.selected_values['landmarks'])
  
          # Correct options based on the type of the shape
          if (instance.has_landmarks and
                  hasattr(instance.landmarks[g], 'labels')):
              # If the shape is a LabelledPointUndirectedGraph ...
              # ...correct colours
              line_colour = []
              marker_face_colour = []
              marker_edge_colour = []
              for lbl in options['with_labels']:
                  id = instance.landmarks[g].labels.index(lbl)
                  line_colour.append(options['line_colour'][id])
                  marker_face_colour.append(options['marker_face_colour'][id])
                  marker_edge_colour.append(options['marker_edge_colour'][id])
              options['line_colour'] = line_colour
              options['marker_face_colour'] = marker_face_colour
              options['marker_edge_colour'] = marker_edge_colour
          else:
              # If shape is PointCloud, TriMesh or PointGraph
              # ...correct colours
              options['line_colour'] = options['line_colour'][0]
              options['marker_face_colour'] = options['marker_face_colour'][0]
              options['marker_edge_colour'] = options['marker_edge_colour'][0]
  
          # Get figure size
          new_figure_size = (
              renderer_options_wid.selected_values['zoom_one'] *
              figure_size[0],
              renderer_options_wid.selected_values['zoom_one'] *
              figure_size[1])
  
          # Render shape with selected options
          save_figure_wid.renderer = render_image(
              image=instance, renderer=save_figure_wid.renderer,
              image_is_masked=image_is_masked, figure_size=new_figure_size,
              **options)
  
          # Update info
          update_info(instance, level, g)
  
      # Define function that updates the info text
      def update_info(image, level, group):
          lvl_app_mod = appearance_model[level]
          lp = 0 if group is None else image.landmarks[group].n_points
          text_per_line = [
              "> Level: {} out of {}.".format(level + 1, n_levels),
              "> {} components in total.".format(lvl_app_mod.n_components),
              "> {} active components.".format(lvl_app_mod.n_active_components),
              "> {:.1f}% variance kept.".format(
                  lvl_app_mod.variance_ratio() * 100),
              "> Reference shape of size {} with {} channel{}.".format(
                  image._str_shape(),
                  image.n_channels, 's' * (image.n_channels > 1)),
              "> {} features.".format(lvl_app_mod.n_features),
              "> {} landmark points.".format(lp),
              "> Instance: min={:.3f}, max={:.3f}".format(image.pixels.min(),
                                                          image.pixels.max())]
          info_wid.set_widget_state(text_per_line=text_per_line)
  
      # Plot variance function
      def plot_variance(name):
          # Clear current figure, but wait until the generation of the new data
          # that will be rendered
          ipydisplay.clear_output(wait=True)
  
          # Get selected level
          level = level_wid.value if n_levels > 1 else 0
  
          # Render
          new_figure_size = (
              renderer_options_wid.selected_values['zoom_one'] * 10,
              renderer_options_wid.selected_values['zoom_one'] * 3)
          plt.subplot(121)
          save_figure_wid.renderer = \
              appearance_model[level].plot_eigenvalues_ratio(
                  figure_id=save_figure_wid.renderer.figure_id, new_figure=False)
          plt.subplot(122)
          save_figure_wid.renderer = \
              appearance_model[level].plot_eigenvalues_cumulative_ratio(
                  figure_id=save_figure_wid.renderer.figure_id, new_figure=False,
                  figure_size=new_figure_size)
          save_figure_wid.renderer.force_draw()
  
      # Create widgets
      model_parameters_wid = LinearModelParametersWidget(
              n_parameters[0], render_function, params_str='Parameter ',
              mode=mode, params_bounds=parameters_bounds, params_step=0.1,
              plot_variance_visible=True, plot_variance_function=plot_variance,
              animation_step=0.5, interval=0., loop_enabled=True,
              continuous_update=False)
      groups_keys, labels_keys = extract_groups_labels_from_image(
          appearance_model[0].mean())
      image_options_wid = ImageOptionsWidget(
          n_channels=appearance_model[0].mean().n_channels,
          image_is_masked=isinstance(appearance_model[0].mean(),
                                     MaskedImage),
          render_function=render_function)
      landmark_options_wid = LandmarkOptionsWidget(
          group_keys=groups_keys, labels_keys=labels_keys,
          type='2D', render_function=render_function)
      renderer_options_wid = RendererOptionsWidget(
          options_tabs=['zoom_one', 'axes', 'numbering_matplotlib', 'legend'],
          axes_x_limits=None, axes_y_limits=None, labels=None,
          render_function=render_function)
      info_wid = TextPrintWidget(text_per_line=[''])
      save_figure_wid = SaveMatplotlibFigureOptionsWidget()
  
      # Group widgets
      tmp_children = [model_parameters_wid]
      if n_levels > 1:
          # Define function that updates options' widgets state
          def update_widgets(change):
              value = change['new']
              # Update model parameters widget
              model_parameters_wid.set_widget_state(
                  n_parameters[value], params_str='Parameter ',
                  allow_callback=False)
  
              # Update landmarks options
              g_keys, l_keys = extract_groups_labels_from_image(
                  appearance_model[value].mean())
              landmark_options_wid.set_widget_state(
                  group_keys=g_keys, labels_keys=l_keys, allow_callback=False)
  
              # Update channels options
              image_options_wid.set_widget_state(
                  n_channels=appearance_model[value].mean().n_channels,
                  image_is_masked=isinstance(
                      appearance_model[value].mean(), MaskedImage),
                  allow_callback=True)
  
          # Create pyramid radiobuttons
          radio_str = OrderedDict()
          for l in range(n_levels):
              if l == 0:
                  radio_str["Level {} (low)".format(l)] = l
              elif l == n_levels - 1:
                  radio_str["Level {} (high)".format(l)] = l
              else:
                  radio_str["Level {}".format(l)] = l
          level_wid = ipywidgets.RadioButtons(
              options=radio_str, description='Pyramid', value=n_levels-1,
              layout=ipywidgets.Layout(width='6cm'))
          level_wid.observe(update_widgets, names='value', type='change')
          level_wid.observe(render_function, names='value', type='change')
          tmp_children.insert(0, level_wid)
      tmp_wid = ipywidgets.HBox(tmp_children)
      options_box = ipywidgets.Tab(
          children=[tmp_wid, image_options_wid, landmark_options_wid,
                    renderer_options_wid, info_wid, save_figure_wid])
      tab_titles = ['Model', 'Image', 'Landmarks', 'Renderer', 'Info', 'Export']
      for (k, tl) in enumerate(tab_titles):
          options_box.set_title(k, tl)
      logo_wid = LogoWidget(style=main_style)
      logo_wid.layout.margin = '0px 10px 0px 0px'
      wid = ipywidgets.HBox([logo_wid, options_box])
  
      # Set widget's style
      wid.box_style = main_style
      wid.layout.border = '2px solid ' + map_styles_to_hex_colours(main_style)
  
      # Display final widget
      final_box = ipywidgets.Box([wid])
      final_box.layout.display = 'flex'
      ipydisplay.display(final_box)
  
      # Trigger initial visualization
      render_function({})
  
  
  def visualize_patch_appearance_model(appearance_model, centers,
                                       n_parameters=5, mode='multiple',
                                       parameters_bounds=(-3.0, 3.0),
                                       figure_size=(7, 7)):
      r"""
      Widget that allows the dynamic visualization of a multi-scale linear
      statistical patch-based appearance model.
  
      Parameters
      ----------
      appearance_model : `list` of `menpo.model.PCAModel` or subclass
          The multi-scale patch-based appearance model to be visualized. Note that
          each level can have different number of components.
      centers : `list` of `menpo.shape.PointCloud` or subclass
          The centers to set the patches around. If the `list` has only one
          `menpo.shape.PointCloud` then this will be used for all appearance model
          levels. Otherwise, it needs to have the same length as
          `appearance_model`.
      n_parameters : `int` or `list` of `int` or ``None``, optional
          The number of principal components to be used for the parameters
          sliders. If `int`, then the number of sliders per level is the minimum
          between `n_parameters` and the number of active components per level.
          If `list` of `int`, then a number of sliders is defined per level.
          If ``None``, all the active components per level will have a slider.
      mode : ``{'single', 'multiple'}``, optional
          If ``'single'``, then only a single slider is constructed along with a
          drop down menu. If ``'multiple'``, then a slider is constructed for each
          parameter.
      parameters_bounds : (`float`, `float`), optional
          The minimum and maximum bounds, in std units, for the sliders.
      figure_size : (`int`, `int`), optional
          The size of the plotted figures.
      """
      # Ensure that the code is being run inside a Jupyter kernel!
      from .utils import verify_ipython_and_kernel
      verify_ipython_and_kernel()
      print('Initializing...')
  
      # Make sure that appearance_model is a list even with one member
      if not isinstance(appearance_model, list):
          appearance_model = [appearance_model]
  
      # Get the number of levels (i.e. number of appearance models)
      n_levels = len(appearance_model)
  
      # Make sure that centers is a list even with one pointcloud
      if not isinstance(centers, list):
          centers = [centers] * n_levels
      elif isinstance(centers, list) and len(centers) == 1:
          centers *= n_levels
  
      # Define the styling options
      main_style = 'success'
  
      # Get the maximum number of components per level
      max_n_params = [ap.n_active_components for ap in appearance_model]
  
      # Check the given number of parameters (the returned n_parameters is a list
      # of len n_scales)
      n_parameters = check_n_parameters(n_parameters, n_levels, max_n_params)
  
      # Define render function
      def render_function(change):
          # Clear current figure, but wait until the generation of the new data
          # that will be rendered
          ipydisplay.clear_output(wait=True)
  
          # Get selected level
          level = level_wid.value if n_levels > 1 else 0
  
          # Compute weights and instance
          parameters = model_parameters_wid.selected_values
          weights = (parameters *
                     appearance_model[level].eigenvalues[:len(parameters)] ** 0.5)
          instance = appearance_model[level].instance(weights)
  
          # Create options dictionary
          options = dict()
          options.update(shape_options_wid.selected_values['lines'])
          options.update(shape_options_wid.selected_values['markers'])
          options.update(
              renderer_options_wid.selected_values['numbering_matplotlib'])
          options.update(renderer_options_wid.selected_values['axes'])
          image_options = dict(image_options_wid.selected_values)
          del image_options['masked_enabled']
          options.update(image_options)
          options.update(patch_options_wid.selected_values)
          options['line_colour'] = options['line_colour'][0]
          options['marker_face_colour'] = options['marker_face_colour'][0]
          options['marker_edge_colour'] = options['marker_edge_colour'][0]
  
          # Get figure size
          new_figure_size = (
              renderer_options_wid.selected_values['zoom_one'] * figure_size[0],
              renderer_options_wid.selected_values['zoom_one'] * figure_size[1])
  
          # Render image with selected options
          save_figure_wid.renderer = render_patches(
              patches=instance.pixels, patch_centers=centers[level],
              renderer=save_figure_wid.renderer, figure_size=new_figure_size,
              **options)
  
          # Update info
          update_info(instance, level)
  
      # Define function that updates the info text
      def update_info(image, level):
          lvl_app_mod = appearance_model[level]
          text_per_line = [
              "> Level: {} out of {}.".format(level + 1, n_levels),
              "> {} components in total.".format(lvl_app_mod.n_components),
              "> {} active components.".format(lvl_app_mod.n_active_components),
              "> {:.1f}% variance kept.".format(
                  lvl_app_mod.variance_ratio() * 100),
              "> Each patch has size {}H x {}W with {} channel{}.".format(
                  image.pixels.shape[3], image.pixels.shape[4],
                  image.pixels.shape[2], 's' * (image.pixels.shape[2] > 1)),
              "> {} features.".format(lvl_app_mod.n_features),
              "> {} landmark points.".format(image.pixels.shape[0]),
              "> Instance: min={:.3f}, max={:.3f}".format(image.pixels.min(),
                                                          image.pixels.max())]
          info_wid.set_widget_state(text_per_line=text_per_line)
  
      # Plot variance function
      def plot_variance(name):
          # Clear current figure, but wait until the generation of the new data
          # that will be rendered
          ipydisplay.clear_output(wait=True)
  
          # Get selected level
          level = 0
          if n_levels > 1:
              level = level_wid.value
  
          # Render
          new_figure_size = (
              renderer_options_wid.selected_values['zoom_one'] * 10,
              renderer_options_wid.selected_values['zoom_one'] * 3)
          plt.subplot(121)
          save_figure_wid.renderer = \
              appearance_model[level].plot_eigenvalues_ratio(
                  figure_id=save_figure_wid.renderer.figure_id, new_figure=False)
          plt.subplot(122)
          save_figure_wid.renderer = \
              appearance_model[level].plot_eigenvalues_cumulative_ratio(
                  figure_id=save_figure_wid.renderer.figure_id, new_figure=False,
                  figure_size=new_figure_size)
          save_figure_wid.renderer.force_draw()
  
      # Create widgets
      model_parameters_wid = LinearModelParametersWidget(
              n_parameters[0], render_function, params_str='Parameter ',
              mode=mode, params_bounds=parameters_bounds, params_step=0.1,
              plot_variance_visible=True, plot_variance_function=plot_variance,
              animation_step=0.5, interval=0., loop_enabled=True,
              continuous_update=False)
      shape_options_wid = Shape2DOptionsWidget(
          labels=None, render_function=None)
      shape_options_wid.line_options_wid.render_lines_switch.button_wid.value = False
      shape_options_wid.add_render_function(render_function)
      patch_options_wid = PatchOptionsWidget(
          n_patches=appearance_model[0].mean().pixels.shape[0],
          n_offsets=appearance_model[0].mean().pixels.shape[1],
          render_function=render_function)
      image_options_wid = ImageOptionsWidget(
          n_channels=appearance_model[0].mean().pixels.shape[2],
          image_is_masked=isinstance(appearance_model[0].mean(), MaskedImage),
          render_function=None)
      image_options_wid.interpolation_checkbox.button_wid.value = False
      image_options_wid.add_render_function(render_function)
      renderer_options_wid = RendererOptionsWidget(
          options_tabs=['zoom_one', 'axes', 'numbering_matplotlib'], labels=None,
          axes_x_limits=None, axes_y_limits=None, render_function=render_function)
      info_wid = TextPrintWidget(text_per_line=[''])
      save_figure_wid = SaveMatplotlibFigureOptionsWidget()
  
      # Group widgets
      tmp_children = [model_parameters_wid]
      if n_levels > 1:
          # Define function that updates options' widgets state
          def update_widgets(change):
              value = change['new']
              # Update model parameters widget
              model_parameters_wid.set_widget_state(
                  n_parameters[value], params_str='Parameter ',
                  allow_callback=False)
  
              # Update patch options
              patch_options_wid.set_widget_state(
                  n_patches=appearance_model[value].mean().pixels.shape[0],
                  n_offsets=appearance_model[value].mean().pixels.shape[1],
                  allow_callback=False)
  
              # Update channels options
              image_options_wid.set_widget_state(
                  n_channels=appearance_model[value].mean().pixels.shape[2],
                  image_is_masked=isinstance(appearance_model[value].mean(),
                                             MaskedImage),
                  allow_callback=True)
  
          # Define pyramid radiobuttons
          radio_str = OrderedDict()
          for l in range(n_levels):
              if l == 0:
                  radio_str["Level {} (low)".format(l)] = l
              elif l == n_levels - 1:
                  radio_str["Level {} (high)".format(l)] = l
              else:
                  radio_str["Level {}".format(l)] = l
          level_wid = ipywidgets.RadioButtons(
              options=radio_str, description='Pyramid', value=n_levels-1,
              layout=ipywidgets.Layout(width='6cm'))
          level_wid.observe(update_widgets, names='value', type='change')
          level_wid.observe(render_function, names='value', type='change')
          tmp_children.insert(0, level_wid)
      tmp_wid = ipywidgets.HBox(tmp_children)
      options_box = ipywidgets.Tab(
          children=[tmp_wid, patch_options_wid, image_options_wid,
                    shape_options_wid, renderer_options_wid, info_wid,
                    save_figure_wid])
      tab_titles = ['Model', 'Patches', 'Channels', 'Shape', 'Renderer', 'Info',
                    'Export']
      for (k, tl) in enumerate(tab_titles):
          options_box.set_title(k, tl)
      logo_wid = LogoWidget(style=main_style)
      logo_wid.layout.margin = '0px 10px 0px 0px'
      wid = ipywidgets.HBox([logo_wid, options_box])
  
      # Set widget's style
      wid.box_style = main_style
      wid.layout.border = '2px solid ' + map_styles_to_hex_colours(main_style)
  
      # Display final widget
      final_box = ipywidgets.Box([wid])
      final_box.layout.display = 'flex'
      ipydisplay.display(final_box)
  
      # Trigger initial visualization
      render_function({})
  
  
  def visualize_morphable_model(mm, n_shape_parameters=5, n_texture_parameters=5,
                                mode='multiple', parameters_bounds=(-15.0, 15.0)):
      r"""
      Widget that allows the dynamic visualization of a 3D Morphable Model.
  
      Parameters
      ----------
      mm : `menpo3d.morhpablemodel.ColouredMorphableModel` or `subclass`
          The multi-scale 3D Morphable Model to be visualized.
      n_shape_parameters : `int` or `list` of `int` or ``None``, optional
          The number of principal components to be used for the shape parameters
          sliders. If `int`, then the number of sliders per level is the minimum
          between `n_parameters` and the number of active components per level.
          If `list` of `int`, then a number of sliders is defined per level.
          If ``None``, all the active components per level will have a slider.
      n_texture_parameters : `int` or `list` of `int` or ``None``, optional
          The number of principal components to be used for the tecture
          parameters sliders. If `int`, then the number of sliders per level is
          the minimum between `n_parameters` and the number of active components
          per level. If `list` of `int`, then a number of sliders is defined per
          level. If ``None``, all the active components per level will have a
          slider.
      mode : ``{'single', 'multiple'}``, optional
          If ``'single'``, then only a single slider is constructed along with a
          drop down menu. If ``'multiple'``, then a slider is constructed for each
          parameter.
      parameters_bounds : (`float`, `float`), optional
          The minimum and maximum bounds, in std units, for the sliders.
      """
      # Ensure that the code is being run inside a Jupyter kernel!
      from .utils import verify_ipython_and_kernel
      verify_ipython_and_kernel()
      print_dynamic('Initializing...')
  
      # Define the styling options
      main_style = 'info'
  
      # Check the given number of parameters
      n_shape_parameters = check_n_parameters(
          n_shape_parameters, 1, [mm.shape_model.n_active_components])
      n_texture_parameters = check_n_parameters(
          n_texture_parameters, 1, [mm.texture_model.n_active_components])
  
      # Define render function
      def render_function(change):
          # Clear current figure
          save_figure_wid.renderer.clear_figure()
          ipydisplay.clear_output(wait=True)
  
          # Compute weights
          shape_weights = shape_model_parameters_wid.selected_values
          shape_weights = (
              shape_weights *
              mm.shape_model.eigenvalues[:len(shape_weights)] ** 0.5)
          texture_weights = texture_model_parameters_wid.selected_values
          texture_weights = (
              texture_weights *
              mm.texture_model.eigenvalues[:len(texture_weights)] ** 0.5)
          instance = mm.instance(shape_weights=shape_weights,
                                 texture_weights=texture_weights)
          # TODO: Is this really needed?
          instance = instance.clip_texture()
  
          # Update info
          update_info(mm, instance)
  
          # Render instance
          save_figure_wid.renderer = instance.view(
              figure_id=save_figure_wid.renderer.figure_id, new_figure=False,
              **mesh_options_wid.selected_values)
  
          # Force rendering
          save_figure_wid.renderer.force_draw()
  
      # Define function that updates the info text
      def update_info(mm, instance):
          text_per_line = [
              "> {} vertices, {} triangles".format(mm.n_vertices,
                                                   mm.n_triangles),
              "> {} shape components ({:.2f}% of variance)".format(
                  mm.shape_model.n_components,
                  mm.shape_model.variance_ratio() * 100),
              "> {} texture channels".format(mm.n_channels),
              "> {} texture components ({:.2f}% of variance)".format(
                  mm.texture_model.n_components,
                  mm.texture_model.variance_ratio() * 100),
              "> Instance: min={:.3f} , max={:.3f}".format(
                  instance.colours.min(), instance.colours.max())]
          info_wid.set_widget_state(text_per_line=text_per_line)
  
      # Plot shape variance function
      def plot_shape_variance(name):
          # Clear current figure, but wait until the generation of the new data
          # that will be rendered
          ipydisplay.clear_output(wait=True)
  
          # Render
          plt.subplot(121)
          mm.shape_model.plot_eigenvalues_ratio()
          plt.subplot(122)
          mm.shape_model.plot_eigenvalues_cumulative_ratio()
          plt.show()
  
      # Plot texture variance function
      def plot_texture_variance(name):
          # Clear current figure, but wait until the generation of the new data
          # that will be rendered
          ipydisplay.clear_output(wait=True)
  
          # Render
          plt.subplot(121)
          mm.texture_model.plot_eigenvalues_ratio()
          plt.subplot(122)
          mm.texture_model.plot_eigenvalues_cumulative_ratio()
          plt.show()
  
      # Create widgets
      shape_model_parameters_wid = LinearModelParametersWidget(
          n_shape_parameters[0], render_function, params_str='Parameter ',
          mode=mode, params_bounds=parameters_bounds, params_step=0.1,
          plot_variance_visible=True, plot_variance_function=plot_shape_variance,
          animation_step=0.5, interval=0., loop_enabled=True)
      texture_model_parameters_wid = LinearModelParametersWidget(
          n_texture_parameters[0], render_function, params_str='Parameter ',
          mode=mode, params_bounds=parameters_bounds, params_step=0.1,
          plot_variance_visible=True, plot_variance_function=plot_texture_variance,
          animation_step=0.5, interval=0., loop_enabled=True)
      mesh_options_wid = Mesh3DOptionsWidget(textured=True,
                                             render_function=render_function)
      info_wid = TextPrintWidget(text_per_line=[''])
      save_figure_wid = SaveMayaviFigureOptionsWidget()
  
      # Group widgets
      model_parameters_wid = ipywidgets.HBox(
          [ipywidgets.Tab([shape_model_parameters_wid,
                           texture_model_parameters_wid])])
      model_parameters_wid.children[0].set_title(0, 'Shape')
      model_parameters_wid.children[0].set_title(1, 'Texture')
      options_box = ipywidgets.Tab([model_parameters_wid, mesh_options_wid,
                                    info_wid, save_figure_wid])
      tab_titles = ['Model', 'Mesh', 'Info', 'Export']
      for (k, tl) in enumerate(tab_titles):
          options_box.set_title(k, tl)
      logo_wid = LogoWidget(style=main_style)
      logo_wid.layout.margin = '0px 10px 0px 0px'
      wid = ipywidgets.HBox([logo_wid, options_box])
  
      # Set widget's style
      wid.box_style = main_style
      wid.layout.border = '2px solid ' + map_styles_to_hex_colours(main_style)
  
      # Display final widget
      final_box = ipywidgets.Box([wid])
      final_box.layout.display = 'flex'
      ipydisplay.display(final_box)
  
      # Trigger initial visualization
      render_function({})
      print_dynamic('')
  
  
  def webcam_widget(canvas_width=640, hd=True, n_preview_windows=5):
      r"""
      Webcam widget for taking snapshots. The snapshots are dynamically previewed
      in a FIFO stack of thumbnails.
  
      Parameters
      ----------
      canvas_width : `int`, optional
          The initial width of the rendered canvas. Note that this doesn't actually
          change the webcam resolution. It simply rescales the rendered image, as
          well as the size of the returned screenshots.
      hd : `bool`, optional
          If ``True``, then the webcam will be set to high definition (HD), i.e.
          720 x 1280. Otherwise the default resolution will be used.
      n_preview_windows : `int`, optional
          The number of preview thumbnails that will be used as a FIFO stack to
          show the captured screenshots. It must be at least 4.
  
      Returns
      -------
      snapshots : `list` of `menpo.image.Image`
          The list of captured images.
      """
      # Ensure that the code is being run inside a Jupyter kernel!
      from .utils import verify_ipython_and_kernel
      verify_ipython_and_kernel()
  
      # Set update function
      images = []
  
      def update(_):
          images.append(wid.selected_values[-1])
  
      # Create widgets
      wid = CameraSnapshotWidget(
          canvas_width=canvas_width, hd=hd, n_preview_windows=n_preview_windows,
          preview_windows_margin=3, style='danger', preview_style='warning',
          render_function=update)
      wid.container.layout.border = (
          '2px solid' + map_styles_to_hex_colours('danger'))
  
      # Display widget
      ipydisplay.display(wid)
  
      # Return
      return images
  
apis: matplotlib.pyplot.gca matplotlib.pyplot.subplot matplotlib.pyplot.show numpy.max numpy.min matplotlib.collections.LineCollection