diff --git a/src/PythonicAPI.md b/src/PythonicAPI.md
index afec87410b86a9ac9e4410d4690e82bdca93798c..17064f7cc99806d00f31a04f60c7d508fcf92999 100644
--- a/src/PythonicAPI.md
+++ b/src/PythonicAPI.md
@@ -401,6 +401,7 @@ See [this tutorial](http://www.vtk.org/Wiki/VTK/Tutorials/3DDataTypes) for a bri
 [Blow](/PythonicAPI/Visualization/Blow) | Ten frames from a blow molding finite element analysis.
 [CameraModel1](/PythonicAPI/Visualization/CameraModel1) | Illustrate camera movement around the focal point.
 [CameraModel2](/PythonicAPI/Visualization/CameraModel2) | Illustrate camera movement centered at the camera position.
+[CaptionActor2D](/PythonicAPI/Visualization/CaptionActor2D) | Draw a caption/bubble pointing to a particular point.
 [ClipSphereCylinder](/PythonicAPI/VisualizationAlgorithms/ClipSphereCylinder) | A plane clipped with a sphere and an ellipse. The two transforms place each implicit function into the appropriate position. Two outputs are generated by the clipper.
 [ColoredAnnotatedCube](/PythonicAPI/Visualization/ColoredAnnotatedCube) | How to color the individual faces of an annotated cube.
 [CollisionDetection](/PythonicAPI/Visualization/CollisionDetection) | Collison between two spheres.
@@ -463,6 +464,8 @@ See [this tutorial](http://www.vtk.org/Wiki/VTK/Tutorials/3DDataTypes) for a bri
 | Example Name | Description | Image |
 | -------------- | ------------- | ------- |
 [BackgroundImage](/PythonicAPI/Images/BackgroundImage) | Display an image as the "background" of a scene, and render a superquadric in front of it.
+[MarkKeypoints](/PythonicAPI/Images/MarkKeypoints) | Mark keypoints in an image.
+
 
 ## Image Processing
 
diff --git a/src/PythonicAPI/IO/ImportToExport.py b/src/PythonicAPI/IO/ImportToExport.py
index f9d33190f77d4d880a813867068ba0609c298c6d..8c90aaf2e3f29f067c75f7e9734701d0affbdd4e 100644
--- a/src/PythonicAPI/IO/ImportToExport.py
+++ b/src/PythonicAPI/IO/ImportToExport.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 from pathlib import Path
 
diff --git a/src/PythonicAPI/ImageProcessing/HybridMedianComparison.py b/src/PythonicAPI/ImageProcessing/HybridMedianComparison.py
index 2ed525b9597cbf77d113624ba5e00ae02d455224..5234ea92f432184df801d13cc306cd5611f5def3 100755
--- a/src/PythonicAPI/ImageProcessing/HybridMedianComparison.py
+++ b/src/PythonicAPI/ImageProcessing/HybridMedianComparison.py
@@ -120,7 +120,7 @@ def main():
     iren = vtkRenderWindowInteractor()
     iren.render_window = ren_win
     style = vtkInteractorStyleImage()
-    iren.interactor_stype = style
+    iren.interactor_style = style
 
     viewports = dict()
     VP_Params = namedtuple('VP_Params', ['viewport', 'border'])
diff --git a/src/PythonicAPI/Images/MarkKeypoints.py b/src/PythonicAPI/Images/MarkKeypoints.py
new file mode 100644
index 0000000000000000000000000000000000000000..0ae9209024548fd7e2687010eea425e7cf29eed1
--- /dev/null
+++ b/src/PythonicAPI/Images/MarkKeypoints.py
@@ -0,0 +1,163 @@
+#!/usr/bin/env python3
+
+from dataclasses import dataclass
+
+# noinspection PyUnresolvedReferences
+import vtkmodules.vtkInteractionStyle
+# noinspection PyUnresolvedReferences
+import vtkmodules.vtkRenderingOpenGL2
+from vtkmodules.vtkCommonColor import vtkNamedColors
+from vtkmodules.vtkCommonTransforms import vtkTransform
+from vtkmodules.vtkFiltersGeneral import vtkTransformPolyDataFilter
+from vtkmodules.vtkImagingSources import vtkImageCanvasSource2D
+from vtkmodules.vtkInteractionStyle import vtkInteractorStyleImage
+from vtkmodules.vtkRenderingCore import (
+    vtkActor2D,
+    vtkCoordinate,
+    vtkImageActor,
+    vtkPolyDataMapper2D,
+    vtkRenderWindowInteractor,
+    vtkRenderWindow,
+    vtkRenderer
+)
+from vtkmodules.vtkRenderingFreeType import vtkVectorText
+
+
+def main():
+    colors = vtkNamedColors()
+
+    draw_color1 = tuple(colors.GetColor3ub('DimGray'))
+    draw_color2 = tuple(colors.GetColor3ub('HotPink'))
+
+    # Create a blank, gray image.
+    drawing = vtkImageCanvasSource2D(scalar_type=ImageCanvasSource2D.ScalarType.VTK_UNSIGNED_CHAR,
+                                     number_of_scalar_components=3,
+                                     extent=(0, 20, 0, 50, 0, 0), draw_color=draw_color1)
+    drawing.FillBox(0, 20, 0, 50)
+
+    # Draw a circle of radius 5 centered at (9,10).
+    drawing.draw_color = draw_color2
+    drawing.DrawCircle(9, 10, 5)
+
+    actor = vtkImageActor()
+    drawing >> actor.mapper
+
+    ren = vtkRenderer(background=colors.GetColor3d('SkyBlue'),
+                      background2=colors.GetColor3d('MidnightBlue'),
+                      gradient_background=2)
+    ren_win = vtkRenderWindow(window_name='MarkKeypoints', number_of_layers=2)
+    ren_win.AddRenderer(ren)
+    ren_win.AddRenderer(ren)
+    iren = vtkRenderWindowInteractor()
+    iren.render_window = ren_win
+
+    ren.AddActor(actor)
+
+    ren_win.Render()
+
+    style = MyStyle()
+    iren.interactor_style = style
+    style.default_renderer = ren
+    style.current_renderer = ren
+    iren.Start()
+
+
+class MyStyle(vtkInteractorStyleImage):
+
+    def __init__(self):
+        super().__init__()
+
+        self.AddObserver('LeftButtonPressEvent', self.OnLeftButtonDown)
+        self.count = 0
+
+    def OnLeftButtonDown(self, obj, event):
+        self.interactor.picker.Pick(self.interactor.GetEventPosition()[0],
+                                    self.interactor.GetEventPosition()[1],
+                                    0,
+                                    self.current_renderer)
+        picked = self.interactor.picker.pick_position
+        self.add_number(picked)
+
+        # Forward events.
+        super().OnLeftButtonDown()
+
+        super().interactor.Render()
+
+    def add_number(self, p):
+        colors = vtkNamedColors()
+        p = list(p)
+        if p[0] == 0 and p[1] == 0:
+            # Not in the box.
+            return
+        s = f'adding marker at: {p[0]:6.4f} {p[1]:6.4f}'
+
+        # # Normally, with an image you would do:
+        # s = self.image.spacing
+        # o = self.image.origin
+        # p[0] = int((p[0] - o[0]) / s[0] + 0.5)
+        # p[1] = int((p[1] - o[1]) / s[1] + 0.5)
+        # Here we do:
+        p[0] = int(p[0]) + 0.5
+        p[1] = int(p[1]) + 0.5
+        s += f' -> {p[0]:3.1f} {p[1]:3.1f}'
+
+        # Create an actor for the text
+        text_source = vtkVectorText(text=str(self.count))
+        # Get the bounds of the text.
+        text_source.update()
+        bounds = text_source.output.bounds
+        # Transform the polydata to be centered over the pick position.
+        center = (0.5 * (bounds[1] + bounds[0]), 0.5 * (bounds[3] + bounds[2]), 0.0)
+        trans = vtkTransform()
+        trans.Translate(-center[0], -center[1], 0)
+        trans.Translate(p[0], p[1], 0)
+
+        tpd = vtkTransformPolyDataFilter(transform=trans)
+
+        coordinate = vtkCoordinate(coordinate_system=Coordinate.CoordinateSystem.VTK_WORLD)
+
+        # Create a mapper.
+        mapper = vtkPolyDataMapper2D(transform_coordinate=coordinate)
+        text_source >> tpd >> mapper
+
+        actor = vtkActor2D(mapper=mapper)
+        actor.property.color = colors.GetColor3d('Yellow')
+
+        self.current_renderer.AddViewProp(actor)
+        print(f'For point: {self.count:3d} {s}')
+
+        self.count += 1
+
+
+@dataclass(frozen=True)
+class Coordinate:
+    @dataclass(frozen=True)
+    class CoordinateSystem:
+        VTK_DISPLAY: int = 0
+        VTK_NORMALIZED_DISPLAY: int = 1
+        VTK_VIEWPORT: int = 2
+        VTK_NORMALIZED_VIEWPORT: int = 3
+        VTK_VIEW: int = 4
+        VTK_POSE: int = 5
+        VTK_WORLD: int = 6
+        VTK_USERDEFINED: int = 7
+
+
+@dataclass(frozen=True)
+class ImageCanvasSource2D:
+    @dataclass(frozen=True)
+    class ScalarType:
+        VTK_CHAR: int = 2
+        VTK_UNSIGNED_CHAR: int = 3
+        VTK_SHORT: int = 4
+        VTK_UNSIGNED_SHORT: int = 5
+        VTK_INT: int = 6
+        VTK_UNSIGNED_INT: int = 7
+        VTK_LONG: int = 8
+        VTK_UNSIGNED_LONG: int = 9
+        VTK_FLOAT: int = 10
+        VTK_DOUBLE: int = 11
+
+
+if __name__ == '__main__':
+    main()
diff --git a/src/PythonicAPI/Images/TestMarkKeypoints.png b/src/PythonicAPI/Images/TestMarkKeypoints.png
new file mode 100644
index 0000000000000000000000000000000000000000..7d8740479c470b88d16039ed755b657981281553
Binary files /dev/null and b/src/PythonicAPI/Images/TestMarkKeypoints.png differ
diff --git a/src/PythonicAPI/Picking/HighlightPickedActor.py b/src/PythonicAPI/Picking/HighlightPickedActor.py
index 20ccb746c3793295becc6bea3bd95da83f1d89a9..6f4052368be17813a357ff5b7ba9ce9abc1d437d 100755
--- a/src/PythonicAPI/Picking/HighlightPickedActor.py
+++ b/src/PythonicAPI/Picking/HighlightPickedActor.py
@@ -101,13 +101,13 @@ class MouseInteractorHighLightActor(vtkInteractorStyleTrackballCamera):
     def __init__(self, parent=None):
         super().__init__()
 
-        self.AddObserver("LeftButtonPressEvent", self.left_button_press_event)
+        self.AddObserver("LeftButtonPressEvent", self.LeftButtonPressEvent)
 
         self.new_picked_actor = None
         self.last_picked_actor = None
         self.last_picked_property = vtkProperty()
 
-    def left_button_press_event(self, obj, event):
+    def LeftButtonPressEvent(self, obj, event):
         click_pos = self.interactor.GetEventPosition()
 
         picker = vtkPropPicker()
@@ -134,7 +134,7 @@ class MouseInteractorHighLightActor(vtkInteractorStyleTrackballCamera):
             # Save the last picked actor.
             self.last_picked_actor = self.new_picked_actor
 
-        self.OnLeftButtonDown()
+        super().OnLeftButtonDown()
         return
 
 
diff --git a/src/PythonicAPI/Picking/HighlightWithSilhouette.py b/src/PythonicAPI/Picking/HighlightWithSilhouette.py
index 98ce0e66a0cbb380503501081c21b598339fe081..229b14a7ee6c561a83d9ad2e900d469e605ed76e 100755
--- a/src/PythonicAPI/Picking/HighlightWithSilhouette.py
+++ b/src/PythonicAPI/Picking/HighlightWithSilhouette.py
@@ -120,13 +120,13 @@ class MouseInteractorHighLightActor(vtkInteractorStyleTrackballCamera):
     def __init__(self, silhouette=None, silhouette_actor=None):
         super().__init__()
 
-        self.AddObserver("LeftButtonPressEvent", self.on_left_button_down)
+        self.AddObserver("LeftButtonPressEvent", self.OnLeftButtonDown)
 
         self.last_picked_actor = None
         self.silhouette = silhouette
         self.silhouette_actor = silhouette_actor
 
-    def on_left_button_down(self, obj, event):
+    def OnLeftButtonDown(self, obj, event):
         click_pos = self.interactor.GetEventPosition()
 
         #  Pick from this location.
@@ -144,7 +144,7 @@ class MouseInteractorHighLightActor(vtkInteractorStyleTrackballCamera):
             self.GetDefaultRenderer().AddActor(self.silhouette_actor)
 
         #  Forward events
-        self.OnLeftButtonDown()
+        super().OnLeftButtonDown()
         return
 
     def set_silhouette(self, silhouette):
diff --git a/src/PythonicAPI/Visualization/CaptionActor2D.py b/src/PythonicAPI/Visualization/CaptionActor2D.py
new file mode 100644
index 0000000000000000000000000000000000000000..c8aa93fa2d340b2dc15bbb83f5089651c1335a44
--- /dev/null
+++ b/src/PythonicAPI/Visualization/CaptionActor2D.py
@@ -0,0 +1,143 @@
+#!/usr/bin/env python3
+
+from dataclasses import dataclass
+
+# noinspection PyUnresolvedReferences
+import vtkmodules.vtkInteractionStyle
+# noinspection PyUnresolvedReferences
+import vtkmodules.vtkRenderingOpenGL2
+from vtkmodules.vtkCommonColor import vtkNamedColors
+from vtkmodules.vtkImagingSources import vtkImageCanvasSource2D
+from vtkmodules.vtkInteractionStyle import vtkInteractorStyleImage
+from vtkmodules.vtkRenderingAnnotation import vtkCaptionActor2D
+from vtkmodules.vtkRenderingCore import (
+    vtkImageActor,
+    vtkRenderWindowInteractor,
+    vtkRenderWindow,
+    vtkRenderer
+)
+
+
+def main():
+    colors = vtkNamedColors()
+
+    draw_color1 = tuple(colors.GetColor3ub('DimGray'))
+    draw_color2 = tuple(colors.GetColor3ub('HotPink'))
+
+    # Create a blank, gray image.
+    drawing = vtkImageCanvasSource2D(scalar_type=ImageCanvasSource2D.ScalarType.VTK_UNSIGNED_CHAR,
+                                     number_of_scalar_components=3,
+                                     extent=(0, 20, 0, 50, 0, 0), draw_color=draw_color1)
+    drawing.FillBox(0, 20, 0, 50)
+
+    # Draw a circle of radius 5 centered at (9,10).
+    drawing.draw_color = draw_color2
+    drawing.DrawCircle(9, 10, 5)
+
+    actor = vtkImageActor()
+    drawing >> actor.mapper
+
+    ren = vtkRenderer(background=colors.GetColor3d('SkyBlue'),
+                      background2=colors.GetColor3d('MidnightBlue'),
+                      gradient_background=2)
+    ren_win = vtkRenderWindow(window_name='CaptionActor2D', number_of_layers=2)
+    ren_win.AddRenderer(ren)
+    ren_win.AddRenderer(ren)
+    iren = vtkRenderWindowInteractor()
+    iren.render_window = ren_win
+
+    ren.AddActor(actor)
+
+    ren_win.Render()
+
+    style = MyStyle()
+    iren.interactor_style = style
+    style.default_renderer = ren
+    style.current_renderer = ren
+    iren.Start()
+
+
+class MyStyle(vtkInteractorStyleImage):
+
+    def __init__(self):
+        super().__init__()
+
+        self.AddObserver('LeftButtonPressEvent', self.OnLeftButtonDown)
+        self.count = 0
+
+    def OnLeftButtonDown(self, obj, event):
+        self.interactor.picker.Pick(self.interactor.GetEventPosition()[0],
+                                    self.interactor.GetEventPosition()[1],
+                                    0,
+                                    self.current_renderer)
+        picked = self.interactor.picker.pick_position
+        self.add_number(picked)
+
+        # Forward events.
+        super().OnLeftButtonDown()
+
+        super().interactor.Render()
+
+    def add_number(self, p):
+        colors = vtkNamedColors()
+        p = list(p)
+        if p[0] == 0 and p[1] == 0:
+            # Not in the box.
+            return
+        s = f'adding marker at: {p[0]:6.4f} {p[1]:6.4f}'
+
+        # # Normally, with an image you would do:
+        # s = self.image.spacing
+        # o = self.image.origin
+        # p[0] = int((p[0] - o[0]) / s[0] + 0.5)
+        # p[1] = int((p[1] - o[1]) / s[1] + 0.5)
+        # Here we do:
+        p[0] = int(p[0]) + 0.5
+        p[1] = int(p[1]) + 0.5
+        s += f' -> {p[0]:3.1f} {p[1]:3.1f}'
+
+        # Create an actor for the text
+        caption_actor = vtkCaptionActor2D(caption=str(self.count), attachment_point=p, border=False,
+                                          three_dimensional_leader=False)
+        caption_actor.caption_text_property.bold = False
+        caption_actor.caption_text_property.italic = False
+        caption_actor.caption_text_property.shadow = False
+
+        self.current_renderer.AddViewProp(caption_actor)
+        print(f'For point: {self.count:3d} {s}')
+
+        self.count += 1
+
+
+@dataclass(frozen=True)
+class Coordinate:
+    @dataclass(frozen=True)
+    class CoordinateSystem:
+        VTK_DISPLAY: int = 0
+        VTK_NORMALIZED_DISPLAY: int = 1
+        VTK_VIEWPORT: int = 2
+        VTK_NORMALIZED_VIEWPORT: int = 3
+        VTK_VIEW: int = 4
+        VTK_POSE: int = 5
+        VTK_WORLD: int = 6
+        VTK_USERDEFINED: int = 7
+
+
+@dataclass(frozen=True)
+class ImageCanvasSource2D:
+    @dataclass(frozen=True)
+    class ScalarType:
+        VTK_CHAR: int = 2
+        VTK_UNSIGNED_CHAR: int = 3
+        VTK_SHORT: int = 4
+        VTK_UNSIGNED_SHORT: int = 5
+        VTK_INT: int = 6
+        VTK_UNSIGNED_INT: int = 7
+        VTK_LONG: int = 8
+        VTK_UNSIGNED_LONG: int = 9
+        VTK_FLOAT: int = 10
+        VTK_DOUBLE: int = 11
+
+
+if __name__ == '__main__':
+    main()
diff --git a/src/Testing/Baseline/PythonicAPI/Images/TestMarkKeypoints.png b/src/Testing/Baseline/PythonicAPI/Images/TestMarkKeypoints.png
new file mode 100644
index 0000000000000000000000000000000000000000..9c77bcb63757be8b0719f29c23e9c015864676d3
--- /dev/null
+++ b/src/Testing/Baseline/PythonicAPI/Images/TestMarkKeypoints.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e04cb920f2c81398d2454c749e3d9f2cb6f3a89ea589972f827713b7a7c04f74
+size 44688
diff --git a/src/Testing/Baseline/PythonicAPI/Visualization/TestCaptionActor2D.png b/src/Testing/Baseline/PythonicAPI/Visualization/TestCaptionActor2D.png
new file mode 100644
index 0000000000000000000000000000000000000000..9c77bcb63757be8b0719f29c23e9c015864676d3
--- /dev/null
+++ b/src/Testing/Baseline/PythonicAPI/Visualization/TestCaptionActor2D.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e04cb920f2c81398d2454c749e3d9f2cb6f3a89ea589972f827713b7a7c04f74
+size 44688