Documentation/Labs/Improving Markups

From Slicer Wiki
Revision as of 19:18, 4 March 2019 by Lasso (talk | contribs)
Jump to: navigation, search
Home < Documentation < Labs < Improving Markups

Overview

The goal of this page is to support improvements to the Markups Module for fiducial placement. This page will document improvements suggested by users and developers and coordinate efforts to address them.

History

Markups, also known as annotations (and almost the same as VTK widgets) proved to be very difficult to design well. First-generation VTK widgets were simple but too monolithic, their behavior and appearance was not customizable enough. Second-generation VTK widgets took a completely new approach, separated "widget" from "representation" allowing much more customization, but also resulting in very complex widgets, with very poor performance in some cases (mainly because many classes are used to implement simple widgets and there is a complex and costly synchronization of states between them). Widgets in Slicer are similarly problematic. First attempt resulted in "Annotations" module, which was very complex to use and develop and also very unstable and so most widgets had to be disabled and only ruler (line) and ROI widgets remained. "Markups" module was the second attempt. The project ran out of time and only markup fiducials were implemented. Implementation is complicated, which makes adding new features very difficult, it has serious issues (sometimes points cannot be clicked or hidden points can be clicked), and it becomes unusably slowe after placing 100-200 points. There have been additional attempts to rework widgets, but so far they have all failed. We don't give up, so here we are again.

Design considerations

Data synchronization between multiple classes (VTK and MRML nodes, instances for each viewer, widget and representation) has proven to be one of the fundamental issues. It would have been nice to create generic VTK-only widgets and use them in Slicer, but the additional layer of abstraction would have made implementation more complicated and degraded performance. Therefore, we do not use VTK widgets and implement Slicer-specific classes, which directly use information stored in MRML nodes.

Separation between "widget" (behavior) and "representation" (appearance) is a good concept, but in second-generation VTK widgets, widgets and representations are too much separated (they don't access each other's state but communicate with state transitions and events). This scheme may be useful for client-server visualization, but it is inefficient and too complicated to use within a simple application. Therefore, we need to split classes in a way that minimizes need for manual synchronization. Separation of "data" (shared globally in the whole scene, such as widget point positions) must be separated from "display" (properties that control how a widget should appear in each view) works very well for displayable MRML nodes and chosen to be used for widgets as well. Data adds thin wrapper over VTK data object (e.g., vtkPointSet), which allows the same data object used in multiple renderers and alleviate any need for synchronizing point lists. State information (even low-level detail, such as markup that the user is currently hovered over) is stored in markup data and display nodes.

Performance of markups module was very slow because each clickable point was implemented by a handle widget. Each handle widget requires instantiation of several classes per views (widget, representation, actors, pickers, etc.), many view observations, and trigger various expensive synchronization mechanisms when anything is changed. Instead, a single glyph actor is created to display all markups.

Point picking (determining what is under the mouse cursor position) and decision about what to do with is performed in VTK using pickers and a picking manager. Picking manager introduced to allow prioritization of events between multiple widgets. However, this solution was quite complicated (it hijacked events in each picker and ran it through the picking manager to decide if the widget may process the event) and due to its distributed nature, difficult to control. In Slicer widgets, each displayable manager can be queried if it can process a particular event (CanProcessEvent), returning a "distance" value. The displayable manager with the closest distance gets the right to process an event. Within a displayable manager, various flexible strategies can be implemented to decide which particular widget may process the event (taking into account the currently active widget, the interaction state of the application, etc.). This centralized solution allows reducing the number of picking operations, for example the interactor style can do a quick approximate picking using the Z buffer, then displayable managers may decide to use a more accurate picker (e.g., vtkCellPicker) and use the result in all the widgets that they manage.

In the old markups module, labels were rendered as polygons in 3D. This made their appearance very low quality (since anti-aliasing is problematic) and they often very hard to see because they are partially occluded by various 3D objects. We considered rendering the text in a texture and display as a semi-transparent rectangle by a 3D actor, but image quality still would have been an issue (texture resolution would need to match screen size and resolution) and occlusion by other 3D objects could still would have made it difficult to see some labels. Therefore, we decided to render labels as 2D text actors using existing VTK mappers. 2D actors can be displayed in high quality but they appear as a separate layer over 3D objects (they could appear very disconnected from the 3D glyphs), which is especially confusing when the markup point is occluded by other objects. Using VTK's fast point visibility checks (based on Z buffer) and label placement mapper features, the labels can be rendered acceptably well (and with the improvement of the mapper in VTK, label display will improve in Slicer, too).

Persistence: it should be possible to save all annotations in the scene (without storage node), to avoid having lots of tiny files (with just a few numbers in each). On the other hand, it would be nice to be able to save annotations in a standard file format and not just in MRML scene. It would be nice to be able to save group of markups to a single file, but unfortunately storing multiple nodes in a single file is problematic. We could add importers/exporters for standard annotation file formats (DICOM, AIM, ...). For now, all markups still use markup fiducial's storage node

Class roles & responsibilities (https://github.com/Kitware/vtk-js/issues/682)

  • MRML data and display nodes (MVC: model, VTK: source) - vtkMRMLMarkupsNode & vtkMRMLMarkupsDisplayNode
    • Stores point positions, curve interpolation type, etc.
    • One instance shared between all renderers.
  • MRML display node (MVC: model, VTK: source) - vtkMRMLMarkupsNode
    • Stores point positions, curve interpolation type, etc.
    • One instance shared between all renderers.
    • One markups may appear in various views in different ways, by defining various display nodes.
  • Widget (MVC: controller) - vtkSlicerAbstractWidget (similar to vtkAbstractWidget)
    • It can decide if it can process a user interface event (mouse move, click, etc.)
    • It can process a user interface event by translating it to a widget event and then executing that (add/delete point, jump to a point, etc.).
    • There is one widget per display node in each renderer.
    • Maintains a very simple internal state (idle, places a point, being over some component of a widget)
    • Keeps a reference to a widget representation.
  • WidgetRepresentation (MVC: view, VTK: actor) - vtkSlicerAbstractWidgetRepresentation (similar to vtkWidgetRepresentation)
    • VTK actor that can be added to a renderer.
    • It can tell if an event position is near some component of the widget.
    • Stores VTK rendering pipeline (actors, mappers, filters).
    • There is one widget representation per widget.
    • There are different representation for 2D and 3D views.

Features

Widget types:

  • point set: DONE
  • angle: DONE
  • curve (open and closed, with different interpolations): DONE
  • line (arrow, ruler): partially done (it is still just a simple line, no length or arrow tip is displayed)
  • ROI (may be 2D only: ellipse, circle, rectangle)
  • bidimensional
  • plane
  • slice intersection (show slice intersections and allow shift/rotate them), crosshair
  • linear transforms (2D: similar to vtkAffineWidget; 3D: current transform widget)
  • ROI-based image window width/level

Functions:

  • Highlight/display info when hovering over: DONE
  • Delete using keyboard shortcut or right-click: DONE (hover over a point and click DEL key)
  • Select entire markup or just a point: DONE
  • Restricting placement and motion to other nodes (model surface, volume rendered image surface, etc.): partially done - point picking and movement in 3D view is restricted to visible surfaces (work well for volume rendered surfaces, too)
  • One click 3D angle, length measurements from landmarks (latter is already available)
  • Allow adding fiducials with undefined position to allow creating a template with only fiducial labels
  • Fine control for fiducial placement/modifications by keyboard strokes
  • Improved scaling of 3D glyphs and 2D points. For example, fixed in screen size, fixed relative to voxel size of the data set, etc.
  • Projection display in slice views (similar to model projections, markup point projection)
  • Right-click menu (delete, jump to slice, fit slice views to main axes)
  • Compute metrics (length, surface, diameter, etc.), allow them to be exported into table or other standard annotation file formats
  • Undo modifications
  • Sensible behavior when non-linear transform is applied

Status

Since there are many small issues and ideas, we do not yet track the items using the Slicer bugtracker but use this spreadsheet: https://1drv.ms/x/s!Arm_AFxB9yqHtvxAgdAfdLS_GHsOxw

References