6.6. SUMA Programming Notes¶
A pitiful set of tips to trick you into venturing down the SUMA code maze.
Any of the varied objects that SUMA can render. DOs include things as varied as surface objects (SOs), volume objects (VOs), and NIML Displayable Objects (NIDOs) which can be text, images, etc.
The set of object types is enumerated in
DOs are tucked into structure
SUMA_DO and stored in global
consists of the Object Pointer
OP (void :literal:)`, its
enumerated type, and its coordinate type. Often, objects are referred
to by their index into that global vector (see macros with iDO in
All DOs can be cast into a structure called
which is considerably more useful than a void pointer. For use
examples, see macros and functions with ADO in their name. Also, make
sure any newly defined DO structure begins with the same elements as
Most objects also share common properties such as an idcode, a label,
a state, etc. I highly recommend you access object properties through
macros or function calls. See the likes of functions
ADO_STATE. Usually, all caps
names are hints that they are macros.
For an object to be considered for display in a particular viewer, it must be registered with it. When a viewer is placed in a certain viewing state, all objects registered with that viewer and of a matching state will be eligible for rendering, i.e. they would be rendered unless they are hidden from view by the user’s choice.
Datasets are represented by the
SUMA_DSET structure with
much of the meat stored in the NI_group
*ngr part of the
structure. All datasets in SUMA are stored in a linked list called
DsetList and present in the global Common Fields structure
Originally datasets could not be displayed on their own: A surface dataset required a surface upon which it would be displayed. For this reason, they were not considered Displayable Objects.
Graph datasets (i.e. matrices, connectivity datasets) ruined that neat
distinction between DSETs and DOs. A graph is a dataset that can
always be displayed as a matrix and as a 3D graph when some
coordinates are assigned to each node of the graph. So a graph dataset
is also a displayable object that can be rendered in multiple ways
(read states). So when a graph dataset is loaded, it is stored in
DsetList, but place holder DOs are also added to the
SUMAg_DOv, as discussed in Graph Link Displayable
Here is a sample of the sequence of events when a dataset is loaded
onto a surface objects (see
Load Dataset from file
Assign domain parent to be the DO onto which it is loaded
If a graph dataset, ignore surface parenting and proceed in modified manner
Add dset to the list
Setup overlays for this dataset.
Overlays are attached to the displayable object, rather than the dataset.
#. Colorize the color plane corresponding to this dataset, and make the colorplane be the currently selected one
#. Refresh dataset selection list, update controller settings, remix and redisplay
Viewers are the windows in which varied objects are
displayed. Multiple viewers can be linked so that they show objects
from the same angle and so that a selection on one object is
propagated to the extent possible to other viewers. A viewer is always
in a particular state and all objects from that
state and that are registered with the viewer will get displayed. The
SUMA_SurfaceViewer is used to keep track of
rendering parameters, to the extent that they apply to multiple
objects, and of current user selections. The object last selected by
the user is said to be in focus .
States: in general define a category of objects that should be rendered together. At first, the term referred to the deformation state of a surface. Now however, the definition is stretched a bit. Basically objects of the same state get displayed together. Some states, such as “ANY_ANTOMICAL” are special in that objects with such states get displayed in any viewer state that is also tagged as anatomically correct. For instance, tractography or volume objects are displayed in viewer showing pial surfaces, white matter, or smooth white matter surfaces. Some states, such as “TheShadow” are used as place holders and are not meant to be displayed.
All DOs have one or multiple``SUMA_OVERLAYS``, which are the colorized instance of the datasets as they are mapped onto them. These overlays (also called colorplanes) are mixed together to form one final set of colors per elementary object datum (nodes on the surface, edge of a graph, point of a tract, etc.)
Here is a sequence of events that occurs after a new dataset is
loaded. The sequence is loosely based on
Create an overlay pointer given the dataset and its domain. See
Add this plane to the list of overlay planes already defined over this domain, e.g.
Setup the options for colorizing this particular plane
Colorize the plane with
Remix all the color planes on a particular DO and redisplay with
Remixing is handled in
SUMA_MixColors(Viewer)where each DO registered with the viewer will get all of its color planes mixed with
SUMA_MixOverlays(). The resultant colors for each DO are stored in a structure called
SUMA_COLORLIST_STRUCTaccessible from the Viewer’s structure with the likes of
The general outline of the picking process is as follows:
Look for intersection with a visible object There are two methods for intersections in SUMA depending on the type of object being tested for intersection.
The first method is geometric and applies to surfaces, matrices, and volumes. In the geometric approach the click location in the viewer is turned into a pick line in the 3D space in which the object resides. The intersections between its geometric primitives and the line are computed and the primitive closest to the view point is preserved.
The second method uses the graphics engine to render all applicable objects (tracts, 3D graphs, text boxes) into a pick buffer image whereby each object primitive is painted with a unique color (R G B A bytes). The pick buffer is then sampled at the click location and the primitive identified by its color.
You can see the pick buffer, for debugging purposes, by Shift Right-Clicking over the object to be selected. The pick buffer is displayed in the recorder window with color ids starting with reddish hues. Note that I don’t start using very low R G B A values for the first primitives because I would not be able to visually distinguish between them in the pick buffer when debugging.
If an object is intersected, store the intersection in
SUMA_PICK_RESULTand add it to
SelAdowhich is the pick (selection) list inside the surface viewer structure
Repeat 1 & 2 for all remaining visible objects
Sort through all selected objects in the pick list and choose the one having the closest (usually) intersection location to the user’s viewing point.
For geometric intersection approach:
For pick buffer approach:
A collection of comments on some of the oddities in the way certain things are done in SUMA. All for a good reason at some point, including ignorance, but there they are.
The engine function
SUMA_Engine() is used to drive SUMA for
much of user interactions. The function takes a list of engine
structures that direct it to perform various tasks in the listed
order. There are functions to create a new engine list, to add
commands to an engine list (either prepend or append), and of
course SUMA_Engine() to execute the list.
SUMA_Engine() was created with the tought that all user actions
should be scriptable. Most GUI callbacks are mere shells to setup a
command list and call
The big structures are for Displayable Objects
SUMA_SurfaceViewer) , Datasets (
The global variables are all prefixed with
SUMAg_ and the most
relevant ones are:
SUMAg_CF for all SUMA-wide settings and
SUMAg_DOv for all DOs, and
SUMAg_SVv for all
Many large pointers can be shared across objects, viewers, etc. Check existing accessor functions, make your own if need be.
When adding fields to a structure, ponder whether they belong to the dataset level, the object level, the viewer level, or SUMA-wide level. Recall that datasets can be shared across objects, and that some datasets effectively double as displayable objects.
FuncName: Almost all functions explicitly define the function name in
a static variable called FuncName, and they use the macros
SUMA_RETURNe for returning
variables or a void, respectively.
The only exception to this rule would be functions that are called a
large number of times and with relatively brief execution time. If you
follow this scheme, you can check for improperly entered or terminated
AnalyzeTrace -suma_c SUMA*.c ../suma_*.c.
LocalHead: A flag local to most functions that turns on otherwise
hidden debugging messages with macros
TLH is a
shorthand for turning LocalHead on and off locally within a function.
SUMA_DUMP_TRACE: A macro to dump memory allocation table
Structure Contents: Numerous functions with “Info” in the name
create strings detailing the content of a particular structure. Those
functions are usually called by counterparts with “Show” in the
name. Older debugging functions have “Print” in the name.
Functions and macros look for stuff: Look for function and macro names beginning with “SUMA_Which, SUMA_which, or SUMA_WHICH”. Also, look for functions and macros with “_Find or _FIND or _find” in the name. There are lots of them.
Functions and macros to ask about stuff: Look for function and macro names beginning with “SUMA_is”.
On the fly rendering masks with operation such as “Do when mask == 0”
and “Do when mask == 1”, and variables such as
$BRI. See semblance of such a feature with patches and numerous
surfaces - Daniel & Atlases
Tract intersection is done via the picking buffer mechanism so one can imagine implementing the sticky feature in one of the following two ways. When in sticky mode, search the pick buffer for the closest pixel that matches the color of the first pick.
Normally the determination of what was picked from the buffer
involves finding the closest colored pixel to the mouse pointer’s
SUMA_ComputeLineDOsIntersect()) and then reverse
looking up of the object represented by that color
SUMA_WhatWasPicked()). For the sticky picking to work, the
search function has to know to search only for a certain color and
you will probably want to increase the search space around the
pointer considerably from the current level. Also one should ponder
the need to search with preference along the direction of
displacement of the pointer to avoid unexpected jump, think of a
tract that curls upwards and back on itself like a respectable
Another thing to consider is the fact that some tracts don’t go far enough in the bundle they are in and one might actualy want to continue tracking along the bundle itself, or a new tract in the bundle should a stoppage be encountered. So in case of stoppage, one should consider the next closest color in the buffer that is for a tract in the same bundle, adopt the new tract if found and continue along it.
One could consider other scenarios to implement the searches above. For instance, when sticky track picking is desired, only render the tract or bundles of interest (see
SUMA_DrawTractDO()). Or one could decide to categorize at the bundle, rather than the tract level (see
You will also need to see if there is a configuration of
keyboard+click that would put the viewer in Sticky Tract
Mode. Mouse and keyboard inputs are handled in
SUMA_input(). Looking at “case ButtonPress: –> case Button3:”
we see that ControlMask ony (without combination with ShiftMask, or
Alt) is not used up. Similarly with mouse motion (dragging) “case
MotionNotify: –> case SUMA_Button_3_Motion:” and button release
“case ButtonRelease: –> case Button3”.
So here is an outline for implementing this approach:
Setup for adding a flag for being in Sticky Tract Mode.
Per the reasoning above, this should be done at Ctrl+ButtonPress3 and can be encoded as a new value for
SUMA_SurfaceViewerstruct. Search for constant
MASK_MANIP_MODEfor an example on how such modes are set and queried.
However we must allow
MouseModeto simultaneously encode for both Mask Manipulation and Sticky Tract Modes. So to make
MouseModemore easily queried, consider turning it into a bitwise mask. At the moment, it is just a series of integer values. For an example of bitwise mask, see definitions for
UPDATE_ROT_MASKand its ilk, along with the use of
Consider also changing the crosshair from arrow to ‘+’ (perhaps) to indicate that one is in a different mouse manipulation mode. This is now done for drawing ROIs; see
Also, should one only turn Sticky Mode on only when the hit is on a tract?
Modify the search in
SUMA_ComputeLineDOsIntersect()or perhaps only in
SUMA_GetColidInPickBuffer4()to act differently in Tract Sticky mode
Snap out of Tacky Mode once Button3Release happens (regardless of whether or not user still has ctrl down perhaps?)
So we can plan on setting
MouseModein sticky tract mode with ctrl+Button3Press (only if a tract is selected?), modify intersection rules during ctrl+Button_3_Motion, then unset Sticky Tract mode durin Button3Release.
Currently, interactively controlled tract masks are either spheres or boxes that are defined on the fly in the masks table of the tract controller. The intent here is to make it possible to specify a generic mask, let’s say a surface of arbitrary shape as another mask type.
Things we would need to consider:
Intersection of the mask with tracts. For simple masks like sphere and box, the computation of intersections is rapid enough to allow for interactive use. For arbitrary shapes this may not be the case, so it would be wise to keep these objects fixed and preserve the intersection mask for repeated uses. To compute the intersection of a segment with an arbitrary surface, one can use
SUMA_MT_intersect_triangle(). The problem is that one will be looking to intersect the segment of every pair of tract points with the whole surface, so this would make the process horrendously slow. The intersection could be sped up however by first checking the intersection of the segment with the box circuscribing the arbitrary SO ( see
SO->MinDims) and then proceeding for checking the intersection with the arbitrary surface. A similar strategy is carried out for tract with sphere intersection. See function
SUMA_TractMaskIntersect()where you will also be handling the intersection with the arbitraty surface.
See also functions
SUMA_TractMasksIntersect(), and functions
Adding an entry of the mask object in the masks controller table. Some of the table’s fields may not be terribly appropriate for such fixed objects, but I think that is OK. See functions
SUMA_InitMasksTable_row()for tips on how to start. One would also need a way to initiate the loading of an arbitrary mask. Such masks could come from IsoSurface and might be made up of multiple ‘blobs’. In that instance you might consider combining such blobs into one surface object (see option -mergerois+dset in IsoSurface).
As for loading the mask object, you could piggy back on the current Load Masks button, and DriveSuma’s -load_masks option.