Speeding up the use of the Foo Canvas

Basic Features made simple again

The problem: Basic features have been made complex

All ZMapWindowCanvasItems are currently FooCanvasGroups and contain lists of features for background, underlay, feature, and overlay and changing these to be simple FooCanvasItems may offer a significant performance improvement and simplify a lot of code and free up a lot of memory. The initial display of ZMap is dominated by alignments (eg SwissProt and TrEMBL) which typically contain 90% of all features on display.

Complexity is implemented but not used

Inspection of the code reveals that the four lists of canvas items in a ZMapWindowCanvasItem have been programmed but are not used, or are used incorrectly. Existing use of these lists can be found by searching for zmapWindowContainerGetOverlay(), zmapWindowContainerGetUnderlay() or WINDOW_ITEM_UNDERLAY, WINDOW_ITEM_OVERLAY etc.

Removing Canvas Item backgrounds

This looked like an easy way to gain speed as it doubles the number of FooCanvasItems, but implementation reveals no difference in performance.

Caveats

Complexity is not used?

Despite each basic feature consisting of several lists of canvas items these lists are empty and may not cause a significant delay.

Side effects

When alignments get bumped their x-coordinate is changed and extra lines and glyphs are drawn on the canvas. Use of the complex ZMapWindowCanvasItem structure may be necessary to do this.

Some notes about the Item factory and the CanvasItem Underlay list

Underlay is coded as a hack for alignment features but never used

This is not immediately obvious. In zMapWindowAlignmentFeature.c the functions zmap_window_alignment_feature_clear(), zMapWindowAlignmentFeatureGappedDisplay() and zMapWindowAlignmentFeatureUngappedDisplay() operate three different flags to control the used of a local no_gaps_hidden_item canvas item, the intention presumably being to hide an ungapped feature while displaying a gapped alignment. The last one of these uses the [WINDOW_ITEM_UNDERLAY] list as a temporary parent for the item to be removed from display but can never be called as the flag controlling access to this function (alignment->flags.gapped_display) is never set. Note that this code assumes that the underlay list is not used because if it was run it would remove anything in in this list from display.

Alignment features implement a class clear function and the Item Factory references it but it is never called and Transcript features do not implement this function which is still referenced by the Item factory.

zmapWindowFToIFactoryRunSingle() takes an argument FooCanvasItem 'current_item' which is always NULL. This is passed via the run_data structure to the function in the mdule that create and draw canvas items. In the alignment and transcript functions this current_item is tested and if non NULL (ie never) then the clear function is called.

Text Features and the Underlay list

zmapWindowTextFeature.c references the WINDOW_ITEM_UNDERLAY list twice but removing these has no effect as the second one adds a line to 'mark the real extent' but this is controlled by a FALSE flag and never run. The first reference sets the background colour and appears to do nothing not already done by other code.

Other use of the Underlay and Overlay lists

Scanning for references to this data either via the CanvasItem data structure or the access functions reveal very little use:

Removing canvas item background underlay and overlay: what's the difference?

No noticable difference in performance has been observed, which is strange, as every canvas item had a background foo canvas item - the amount of data on the canvas should have been halved.

Implementation

General comments about existing data and code

Canvas Item Class Structure

Due to the technicalities of object instantiation in GLib it's not practical to derive groups and simple items from a single base class. However as foo_canvas_group derives from foo_canvas_item they will all derive from foo_canvas_item so we do have a single base class at the bottom. Note that we cannot have simple canvas items derived from foo_canvas_group and just not use the group data as the foo canvas code treats groups differently from items.

A simple canvas item (eg basic feature or ungapped alignment) will derive from a foo_canvas_item and have no additional lists of features.

ZMapWindowCanvasItem    (contains ->feature, ->item_type)
    foo_canvas_item

A complex canvas item (eg transcript, gapped alignment) will derive from a foo_canvas_group (which derives from a foo_canvas_item) and will have a single list of subfeatures, rather than four as at present. Note that this implies complex features may interleave on the display, but this is not thought to be an issue as transcripts are normally bumped as no overlap and alignments are only displayed complexly (eg with glyphs and colinear lines) when bumped.

ZMapWindowCanvasGroup (contains ->feature, ->item_type, ->item_list)
  foo_canvas_group
    foo_canvas_item
The item_list is a list of foo_canvas_items.

The different types of ZMap canvas item (eg basic feature, transcript_feature) will derive from either ZMapWindowCanvasItem or zmapWindowCanvasGroup. Note that alignments displayed as ungapped will be ZMapWindowCanvasItem and gapped as ZMapWindowCanvasGroup.

Container Classes

These will derive from ZMapWindowContainerGroup as at present. This derives from foo_canvas_group and contains a list of canvas items which in this case contains four foo_canvas_groups for the background, underlay, features, and overlay. Some remarks in passing:

Operating the item factory

Existing code adds canvas items by first creating a ZMapWindowCanvasItem (a foo_canvas_group) and then adding one or mor items to it via the class->add_interval function. Necessarily for simple canvas items we will have to combine these two functions and the drawXXXFeature functions in zmapWindowItemfactory.c can be used to drive this with minimal effort.

Summarise Columns

See also RT #177032, where this text orginated.

Displaying invisible features slows ZMap down

Display time in ZMap is dominated by a few heavily populated columns eg Trembl may have 50k features but the majority of these features are obscured by others and cannot be seen. As operating the foo canvas takes 90% of the display time and a complete feature context can be scanned very quickly (eg 50ms to reverse complement all features, compared with 10sec to display) it may be possible to speed up this display by not adding invisible features to the canvas. Note that due to the display format of alignments (centered rectangles) we can resonably expect to loose up to 98% of features off the display for a column like TrEMBL - if the column is 1000 pixels deep we cannot need to display more than 1000 features to give the same visual appearance. There may still be some overlap caused by the simple scan algorithm.

This is a similar process to the idea of a summary display as to be provided by otterlace on request, but the difference is that ZMap would still have all the data and only real features will be used. The argument for having ZMap perform the necessary calculations is that ZMap knows when features become obscured at different zoom levels as it knows about pixel coordinates.

This process only applies to un-bumped columns and to implement this a column with an appropriate style parameter would be processed before display at the current zoom level. This parameter defines the minimum zoom level at which to apply the summarising algorithm:

[pep-align]
alignment-summarise=0.1

Algorithm

This uses little extra memory and comparisons are fairly quick as there is almost no searching involved. Visibility is computed exactly and we can choose whether to display the items at the top or bottom of the foo canvas heap by sorting in reverse order.

Caveats

BumpCol would have to be changed to display the hidden features rather than moving thier x-coordinate. However bumping already includes adding lines and glyphs to the canvas and this should not be a significant problem.

Functions such as 'List all Column Features' may have to be reviewed if they feed off the canvas data - perhaps some speed gains can be made by drawing features as hidden. (tests show about a 20-25% improvement by hiding all features.

Displaying several featuresets in one column is more complicated especially when we consider that the summarise option is selected by a featureset's style. It is possible so specify a style for a column and this perhaps provides some resolution despite allowing for mis-configuration. However the intention was to speed up the display of featuresets like TrEmbl and SwissProt which normally appear in a column of thier own It may not be worth implementing a complex way of combining featuresets like this, better to specify what happens if this is configured. As a short term measure we will choose to summarise each featureset in a column independantly.

Implementation

Module and scope

This will be a new module zmapWindowContainerSummarise.c and will operate on a ZMapWindowContainerFeatureSet, which may contain more than one featureset from the feature context.

Note that in a view (which contains the feature context) if there are multiple windows open these are not constrained to be at the same zoom level (although you do have to unlock them first) and therefore it is not appropriate to maintain any flags in the feature context relating to this - all state information must be at the level of ZMapWindowCanvasItems.

Choosing featuresets and when

Styles will be used to control whether or not to not display features. This implies that it will be possible to select individual featuresets within a column for summarising. Logically we could summarise columns other than alignments but as there are only two columns thought to be relevant (and they include only one featureset each) this will be phrased as an alignment mode option. GF-splice glyphs look like another good candidate but as they are not rectangular features would require special code.

We can also specify a minimum zoom level to do this at and avoid pointless computation.

Where to attach the filter

We need to have access to canvas coordinates and this implies that we need to home in on the ItemFactory where these are calculated. In zMapWinfowItemFactoryRunSingle() a function ((method)->method)() is called for each type of feature which creates and draws the ZMapWindowCanvasItem and when this is called the x and y coordinates have been calculated and adjusted for score if configured, but are in real world coordinates. The foo canvas provides a function to convert called foo_canvas_w2c().

Filtering calls round this 'method. will allow us a single point of control and provides a clean interface to the rest of the code. There are futher offsets calculated later but as we need feature relative coordinates this is not relevant.

Data structures used

We will use a simple linked list of reactangle structures and these will be freed at the earliest opportunity. None of this has any permanence at all, although it will be held in the Window for use by callbacks.

Results

After implementing the filtering algorithm as above we can see a big reduction in objects drawn and time used:
FeatureSetFeatures drawnTotal features Max list sizeTime usedPrevious Time Ratio
TrEMBL 722 289224 0.206sec 4.865sec 23.6
SwissProt 554 9563 30.125sec 1.606sec 12.8
Given that the list of covered regions stays very short there seems little point in combining these as originally planned. The reduction in time is a little dissappointing - it is related quite directly to the number of items drawn (1/40 for TrEMBL). There may be further gains to be made by implementing a radix sort - with 200k features this would run 18 times as fast. A lower limit will be the time needed to draw 1000 features. As the summarise algorithm is not optimal the number of featuress draw could be reduced by running the summarise algorithm in reverse order on the summarised data.

Some caveats

This has so far been an experiment and there are some issues that need to be resolved:

These imply some performance overhead, although not as great as drawing the whole column in the first place.

Removing canvas items altogether

Where is the performance problem?

Even with 200k features it should be possible to display relatively quickly and perhaps there are delays introduced by interaction with the X server which has a complex protocol involving map realize update and expose events. We have a tree of canvas groups to the column level which consists of no more than a few hundred groups total and this can certainly be processed very quickly. All we have to do is cause GdkDraw functions to be called and mouse events linked to specific features and this can be handled by ZMap without reference to the foo canvas directly.