#coding=utf-8
"""
Classes to implement text overlay.
Includes Corner Annotation, Large Centered Text and
generic text overlay.
"""
import logging
import vtk
logging.basicConfig(level=logging.INFO)
LOGGER = logging.getLogger(__name__)
#pylint:disable = invalid-name, no-member, attribute-defined-outside-init
[docs]class VTKCornerAnnotation:
"""
Wrapper for vtkCornerAnnotaiton class.
"""
def __init__(self):
self.text_actor = vtk.vtkCornerAnnotation()
[docs] def set_text(self, text_list):
"""
Set the text in each of the four corners
:param text_list: Text to display.
[bottom-left, bottom-right, top-left, top-right].
:type text_list: List of 4 strings.
"""
self.validate_input(text_list)
for idx, item in enumerate(text_list):
self.text_actor.SetText(idx, item)
[docs] def get_text(self):
"""
Returns the current list of text annotations
:return: [bottom-left, bottom-right, top-left, top-right]
"""
text = [self.text_actor.GetText(0),
self.text_actor.GetText(1),
self.text_actor.GetText(2),
self.text_actor.GetText(3)]
# Set None values to ''
text_all_str = ['' if x is None else x for x in text]
return text_all_str
[docs] def set_text_on_top_left(self, text):
"""
Set the text on the top-left corner.
:param text: Text to display.
"""
self.text_actor.SetText(2, text)
[docs] def set_text_on_top_right(self, text):
"""
Set the text on the top-right corner.
:param text: Text to display.
"""
self.text_actor.SetText(3, text)
[docs] def set_text_on_bottom_left(self, text):
"""
Set the text on the bottom-left corner.
:param text: Text to display.
"""
self.text_actor.SetText(0, text)
[docs] def set_text_on_bottom_right(self, text):
"""
Set the text on the bottom-right corner.
:param text: Text to display.
"""
self.text_actor.SetText(1, text)
[docs]class VTKTextBase:
"""
Wrapper around vtkTextActor class to set position,
colour, size etc.
"""
[docs] def set_text_string(self, text):
"""
Set the text string.
:param text: text to display."""
self.validate_text_input(text)
self.text_actor.SetInput(text)
[docs] def set_text_position(self, x, y):
"""
Set the x,y coordinates of the text (bottom-left)
:param x: x location in pixels
:param y: y locaiton in pixels
"""
if self.validate_x_y_inputs(x, y):
self.text_actor.SetPosition(x, y)
self.x = x
self.y = y
[docs] def set_font_size(self, size):
"""
Set the font size.
:param size: size in points"""
self.text_actor.GetTextProperty().SetFontSize(size)
[docs] def set_colour(self, r, g, b):
"""
Set the text colour.
:param r: Red (0.0 - 1.0)
:param g: Green (0.0 - 1.0)
:param b: Blue (0.0 - 1.0)
"""
self.text_actor.GetTextProperty().SetColor(r, g, b)
[docs] def validate_text_input(self, text):
"""
Check text input is a valid string.
:param text: Input to validate. """
if isinstance(text, str):
return True
raise TypeError('Text input to VTKText is not a string.')
[docs] def validate_x_y_inputs(self, x, y):
"""
Check that coordinate inputs are valid.
:param x: x location.
:param y: y location """
valid_types = (int, float)
if not isinstance(x, valid_types):
raise TypeError('x input to VTKText is not a valid number')
if not isinstance(y, valid_types):
raise TypeError('y input to VTKText is not a valid number')
return True
[docs]class VTKText(VTKTextBase):
"""
VTKText object that can be placed following a left click event.
Text will rescale if the window resizes, to try and maintain relative
positioning.
:param text: text to display.
:param x: x position (pixels)
:param y: y position (pixels)
:param font_size: Font size
:param colour: Colour, RGB tuple
"""
def __init__(self, text, x, y, font_size=24, colour=(1.0, 0, 0)):
""" Create a VTK text actor.
"""
self.text_actor = vtk.vtkTextActor()
self.text_actor.SetTextScaleModeToViewport()
self.set_text_string(text)
self.set_text_position(x, y)
self.set_font_size(font_size)
r, g, b = colour
self.set_colour(r, g, b)
[docs] def set_parent_window(self, parent_window):
"""
Link the object to a VTKOverlayWindow and set up callbacks.
:param parent_window: VTKOverlayWindow
"""
self.parent_window = parent_window
self.calculate_relative_position_in_window()
self.parent_window.AddObserver('ModifiedEvent',
self.callback_update_position_in_window)
[docs] def calculate_relative_position_in_window(self):
"""
Calculate position relative to the middle of the screen.
Can then be used to re-set the position if the window is
resized.
"""
width, height = self.parent_window.GetRenderWindow().GetSize()
middle_x = width // 2
middle_y = height // 2
self.original_aspect_ratio = width/height
self.x_relative = (self.x - middle_x) / width
self.y_relative = (self.y - middle_y) / height
[docs] def callback_update_position_in_window(self, _obj_unused, _ev_unused):
"""
Update position, maintaing relative distance to the centre
of the background image.
"""
width, height = self.parent_window.GetRenderWindow().GetSize()
middle_x = width // 2
middle_y = height // 2
current_aspect_ratio = width / height
if current_aspect_ratio == self.original_aspect_ratio:
x = middle_x + \
self.x_relative * (height * self.original_aspect_ratio)
y = middle_y + \
self.y_relative * (width / self.original_aspect_ratio)
else:
# Too wide - height sets the x position
if width > height * self.original_aspect_ratio:
x = middle_x + \
self.x_relative * (height * self.original_aspect_ratio)
y = middle_y + self.y_relative * height
# Too tall, width sets the y position
else:
y = middle_y + \
self.y_relative * (width / self.original_aspect_ratio)
x = middle_x + self.x_relative * width
self.set_text_position(x, y)
[docs]class VTKLargeTextCentreOfScreen(VTKTextBase):
"""
Display large text in the centre of the screen.
Useful for error messages/warnings etc.
:param text: text to display.
"""
def __init__(self, text):
self.text_actor = vtk.vtkTextActor()
self.text_actor.SetTextScaleModeToProp()
self.text_actor.GetTextProperty().SetJustificationToCentered()
self.text_actor.GetTextProperty().SetVerticalJustificationToCentered()
self.set_text_string(text)
[docs] def set_parent_window(self, parent_window):
"""
Attach text to a particular window.
:param parent_window: VTKOverlayWindow that message will be
displayed in.
"""
self.parent_window = parent_window
self.parent_window.AddObserver('ModifiedEvent',
self.calculate_text_size)
self.calculate_text_size(None, None)
[docs] def calculate_text_size(self, _obj_unused, _ev_unused):
"""
Calculate the position and size of the text.
Text should span the central half (x & y) of the window.
"""
#pylint:disable=unused-argument
width, height = self.parent_window.GetRenderWindow().GetSize()
self.set_text_position(width/2, height/2)
self.text_actor.SetMinimumSize(width, height)