Labels
From InfovisWiki
Labels are text annotations placed into a rendered area in VTK. VTK permits labeling a non-overlapping subset of labels based on priority and the current camera location. This page is meant to describe the latest evolution of labels in VTK.
Contents |
Current Action Items
-
check performance and add improvements -
replace vtkQtTreeRingLabeler with new functionality -
improve alpha blending on windows -
rotated labels not as crisp as non-rotated labels -
check label justification and placement in qt labels - add a size limit to the Qt label image cache
-
would like qt labeling to use a hard-coded font file to limit the number of test images required for the dashboard. - allow per label text properties (font size, family, color, etc.) similar to orientation
-
allow font family to be set via a string on the textproperty to allow for system fonts to be utilized with qt-labelling.
Example
|
This python code generates 10,000 points with random orientations and renders them the Qt label rendering strategy in order to obtain the picture below. Note that slight overlaps are due to adding a margin to the shape in order to draw rounded rectangles. The text itself will never overlap. Also, note that since the priority by default is the order of the input, most of the labels rendered have lower values. This code is located in VTK/Examples/Infovis/Python/labels.py |
import random
from vtk import *
n = 10000
pd = vtkPolyData()
pts = vtkPoints()
verts = vtkCellArray()
orient = vtkDoubleArray()
orient.SetName('orientation')
label = vtkStringArray()
label.SetName('label')
for i in range(n):
pts.InsertNextPoint(random.random(), random.random(), random.random())
verts.InsertNextCell(1)
verts.InsertCellPoint(i)
orient.InsertNextValue(random.random()*360.0)
label.InsertNextValue(str(i))
pd.SetPoints(pts)
pd.SetVerts(verts)
pd.GetPointData().AddArray(label)
pd.GetPointData().AddArray(orient)
hier = vtkPointSetToLabelHierarchy()
hier.SetInput(pd)
hier.SetOrientationArrayName('orientation')
hier.SetLabelArrayName('label')
hier.GetTextProperty().SetColor(0.0, 0.0, 0.0)
lmapper = vtkLabelPlacementMapper()
lmapper.SetInputConnection(hier.GetOutputPort())
lmapper.SetShapeToRoundedRect()
lmapper.SetBackgroundColor(1.0, 1.0, 0.7)
lmapper.SetBackgroundOpacity(0.8)
lmapper.SetMargin(3)
# Remove the following two lines for
# Freetype rendering (the default)
strategy = vtkQtLabelRenderStrategy()
lmapper.SetRenderStrategy(strategy)
lactor = vtkActor2D()
lactor.SetMapper(lmapper)
mapper = vtkPolyDataMapper()
mapper.SetInput(pd)
actor = vtkActor()
actor.SetMapper(mapper)
ren = vtkRenderer()
ren.AddActor(lactor)
ren.AddActor(actor)
ren.ResetCamera()
win = vtkRenderWindow()
win.AddRenderer(ren)
iren = vtkRenderWindowInteractor()
iren.SetRenderWindow(win)
iren.Initialize()
iren.Start()
|
Implementation
Label rendering strategies
The following are the methods of the vtkLabelRenderStrategy, none of which are implemented the superclass except SetRenderer().
void SetRenderer(vtkRenderer*) bool SupportsRotation() void ComputeLabelBounds(vtkTextProperty*, vtkUnicodeString, double[4]) void ComputeLabelBounds(vtkTextProperty*, vtkStdString, double[4]) void RenderLabel(double[3], vtkTextProperty*, vtkUnicodeString) void RenderLabel(double[3], vtkTextProperty*, vtkStdString) void StartFrame() void EndFrame()
- SupportsRotation() returns whether rotation is supported by the render strategy. If rotation is not supported, the intersection tests in vtkLabelPlacementMapper will not rotate labels.
- ComputeLabelBounds() computes the bounds of a label based on the input text property and string. The bounds (xmin, xmax, ymin, ymax) should reflect the bounds in pixel units if the label was rendered at (0,0) with no rotation. vtkLabelPlacementMapper will take care of rotating the bounding box when testing for intersections.
- RenderLabel() takes care of rendering the label at a particular 3D position in world coordinates. This rendering should take orientation into account by rotating the text around the anchor point.
StartFrame() and EndFrame() are called at the start and end of rendering a set of labels, and should handle any additional logic necessary.
Sequence of events
- vtkPointSetToLabelHierarchy takes a single point set as input. The methods
vtkPointSetToLabelHierarchy::SetLabelArrayName(const char* name) vtkPointSetToLabelHierarchy::SetPriorityArrayName(const char* name) vtkPointSetToLabelHierarchy::SetOrientationArrayName(const char* name) vtkPointSetToLabelHierarchy::SetBoundedSizeArrayName(const char* name)
- are used to set the significant arrays in the label hierarchy. The label array is the only required array for labels to work, and should have the string array used for labeling, although numeric arrays will be automatically converted to strings. The priority array should be numeric and is used to determine (along with distance from the camera) the order the labels are traversed when being placed. The orientation array is used to optionally assign per-label orientation. If it is not specified, all labels in the data set will have the orientation set in the vtkTextProperty. The bounded size array should be a 2-component data array with the length and width of the bounding box for the text in world coordinates. This is useful in things like the tree ring view, where you may want labels to only be drawn if they fit inside the appropriate region of the screen. vtkStackedTreeLayoutStrategy provides appropriate orientation and bounded size arrays named "TextRotation" and "TextBoundedSize".
- The additional method
vtkPointSetToLabelHierarchy::SetTextProperty(vtkTextProperty* tprop)
- is used to associate a default text property with the set of labels.
- To iterate over all the hierarchy inputs, vtkLabelPlacementMapper creates a special type of iterator, vtkLabelHierarchyCompositeIterator, which traverses multiple vtkLabelHierarchyIterators in a round-robin sequence (i.e. ABCABCABC...). The sub-iterator type for each of the input hierarchies is determined by vtkLabelPlacementMapper::SetIteratorType(int). Normally you will want to use the default, vtkLabelHierarchy::QUEUE which has been found to work best in practice.
- For each label iterated in sequence, vtkLabelPlacementMapper uses the label render strategy to find the size of the label. Then it is checked against a set of binned labels for intersection. If there is no intersection, the label is then rendered using the render strategy.
Rendering labels in views and representations
Representations may have one or more instances of vtkPointSetToLabelHierarchy, and send the pipeline connection(s) to vtkRenderView through vtkRenderView::AddLabels(vtkAlgorithmOutput*), which adds it as an input to vtkLabelPlacementMapper.
The following diagram shows which view and representation classes contain the various parts of the label pipeline.
Design discussion
Qt label rendering quality and performance
This image shows the difference between rotated text drawn simply with QPainter::addText() (bottom) and text first converted to a QPainterPath before being drawn (top). Qt's xform example, which draws rotated text, uses the QPainterPath technique to get high quality text rendering.
This image shows an attempt to render horizontal text to an image, then draw a rotated image to the screen with Qt. We confirmed similar bad-looking results for the current vtkTextActor which allows rotation by rotating an OpenGL quad.
If you try scaling up the image by a factor of two, then downsampling it when it is drawn in the painter, it is still unacceptable, as this image shows.
After considerable investigation, the best solution for getting good rendering quality and performance seems to be to render the Qt text to an image, cache it, then draw the image to the screen as needed. For rotated text, the text must be rendered as rotated, requiring larger cached images especially for angles around 45-degrees. The cached images are stored in smaller QImages and drawn to a bigger QImage that is the size of the screen, which is then texture-mapped onto a polygon. We could instead draw each smaller QImage on an OpenGL polygon for even faster performance, but it seems like this solution gives good enough performance.
Also, in order to get good quality on rotated text, it is impossible to use QTextDocument with its formatting features, since it has no mechanism for converting to a QPainterPath, which is needed for good quality. For the time being I've taken out all uses of QTextDocument, but we can decide to add that back in as an option, possibly disallowing rotated text if it is turned on.
The result is the following timing that was performed Sept. 25, 2009. It shows the relative times in each stage of the text rendering process for four different rendering options:
- Qt rendering with both rotated text and ellipsis support for bounding regions.
- Qt rendering with rotated text only.
- Qt rendering with ellipsis support for bounding regions only.
- Qt rendering with no rotated text or bounding regions.
- FreeType rendering (which has no rotated text or bounding regions support).
As you can see, the basic Qt rendering was slightly faster than the equivalent FreeType rendering. Note that this was with an "unlimited" image cache for Qt, performance may drop slightly with limited cache size if scrolling quickly across different regions of the dataset. Also note that the ellipsis support causes a large performance hit; this is because image caching is turned off for this case. There is no caching there because labels may be shortened in many different ways each frame, and it seems unwise to cache all of them. Caching could be added in the future however to get better frame rates.
Application-specific fonts
You can now add a true type font to the QFontDatabase and display text/labels using a much wider variety of font classes. Many true-type fonts are available freely on the web.
A sample code snippet for doing this would be (Note: You have to be using the Qt labelers...):
if(!QApplication::instance())
{
int argc = 0;
new QApplication(argc, 0);
}
QFontDatabase::addApplicationFont("/Data/Infovis/martyb_-_Ridiculous.ttf");
...application code...
vtkViewTheme* const theme = vtkViewTheme::CreateMellowTheme();
theme->GetPointTextProperty()->SetFontFamilyAsString("Ridiculous");
view->ApplyViewTheme(theme);
theme->Delete();
Here are some sample images taken from TestQtLabelStrategy and TestQtTreeRingLabeler in VTK/Views/Testing/Cxx/.





