AIMS developer documentation

For now this document merely contains one or two tips.

See also the PyAims documentation and PyAims tutorial for developing exmaples in python language.

The current document is dedicated to the C++ API. It may be used conjointly with the Doxygen C++ API reference.

C++ API docs

AIMS library C++ API

AIMS is also composed of several sub-projects:

Using graph objects in AIMS

Graphs are generic data structures to represent a set of objects (nodes), which may (or may not) be linked by relations. In a general context, nodes and relations are objects which may have various shapes. This definition is general enough to make graphs able to represent almost any kind of objects collections. This is why we use them in AIMS and Anatomist to represent both structured sets of objects, such as sulci graphs, and less structured sets, such as regions of interests (ROI) sets. The practical interest is to have a generic data structure and IO for a wide range of objects sets, with a common representation.

We use them to represent many types of neuroimaging objects: ROIs, activation clusters, sulci, gyri, fibers, and more abstract object: sulci recognition and morphometry models, and potentially models of the whole world…

In AIMS graphs are represented as Graph objects. This Graph class in C++ may be inherited by more specialized structures in specific applications: in sulci-related algorithms for instance. A graph contains a set of nodes, or vertices, represented in Vertex objects. Relations, or edges, are represented in Edge. All ingerit the generic object API, GenericObject, and more precisely the PropertySet dictionary, which offers both dynamic and builtin properties, and notification capabilities.

File formats

Historically we use the .arg format, an ASCII format which stores the graph structure. Alternately AIMS also supports a python/json-like (.minf) format, and a XML variant. Lastly we experiment a database representation for single or multiple grpahs in SQLite3 format (but this has not be generalized in our applications).

The graph structure file is generally completed by additional data, which are stored in a .data directory. Depending on the nature of the objects contained in the graph nodes and relations, the contents of this directory may vary. It typically stores meshes, voxels lists (Buckets), or volumes. But in specialized frameworks it may contain other objects (models and machine learning objects for sulci model graphs for instance).

The .data directory has generally two forms for Aims graphical objects: compact (“global”) with one file for many objects, or split (“local”) with one file per object, which may produce many little files.

The current .arg format has limitations in its genericity: properties allowed in it must be declared in external syntax files, which have to be loaded (see SyntaxReader).

Prerequisite: cartobase library basics

Reference counting

Graph objects are based on reference counters. These smart pointers are used just like pointers.

#include <cartobase/smart/rcptr.h>

using namespace carto;

int main()
{
  rc_ptr<int>  rc1( new int );
  rc_ptr<int>  rc2 = rc1;
  int a = *rc1 + *rc2;
}

In short, reference-counting pointers share the ownership of an object and destroy it when nobody needs it any longer: one must never delete a reference-counted object, it is done automatically.

Generic objects

GenericObject is the base class for all elements in a graph: graph, node, or relation.

A common, more specific generic object: Dictionary, which is a typedef from std::map<std::string, carto::Object>.

Another more specialized generic dictionary: PropertySet which has the same API but supports more interesting featrures.

Both support the DictionaryInterface API.

Main generic methods:

Object is a reference counter to a GenericObject.

Main classes

Standard handling

Reading

Avoid using the low-level classes in the graph library (GraphReader / GraphParser / GraphWriter).

Instead, use the higher-level AIMS IO:

#include <aims/io/reader.h>
#include <graph/graph/graph.h>
#include <iostream>

using namespace aims;
using namespace std;

int main( int, char** )
{
  Reader<Graph> reader( "filename.arg" );
  Graph         graph;
  try
  {
    reader.read( graph );
  }
  catch( exception & e )
  {
    cerr << e.what() << endl;
  }
}

Iteration on nodes

Graph::iterator i, e = graph.end();
Vertex *v;
for( i=graph.begin(); i!=e; ++i )
{
  v = *i;
  // do something with v
}

Complete example 1 source:

 1#include <aims/getopt/getopt2.h>
 2#include <aims/io/reader.h>
 3#include <graph/graph/graph.h>
 4
 5using namespace aims;
 6using namespace carto;
 7using namespace std;
 8
 9int main( int argc, const char** argv )
10{
11  Reader<Graph> reader;
12  Writer<Graph> writer;
13  AimsApplication	app( argc, argv, "Reads a graph, iterates over its "
14      "nodes and relations" );
15
16  app.addOption( reader, "-i", "input graph" );
17  app.addOption( writer, "-o", "output graph", true );
18
19  try
20    {
21      app.initialize();
22
23      Graph	graph;
24      reader.read( graph );
25      cout << reader.fileName() << " read\n";
26
27      cout << "graph vertices: " << graph.order() << endl;
28      cout << "graph edges   : " << graph.edgesSize() << endl;
29      cout << "graph syntax  : " << graph.getSyntax() << endl;
30
31      Graph::iterator  iv, ev = graph.end();
32      Vertex::iterator ie, ee;
33      Vertex           *v;
34
35      for( iv=graph.begin(); iv!=ev; ++iv )
36      {
37        v = *iv;
38        cout << "vertex " << v << ", syntax: " << v->getSyntax()
39            /* WARNING: for now Vertex::size() is its number of relations
40              (synonym to edgesSize()). It will change in the future but
41              at the moment, to get its properties size, use the following: */
42            << ", properties: " << v->TypedObject<Dictionary>::size()
43            << ", edges: " << v->edgesSize() << " ( ";
44        for( ie=v->begin(), ee=v->end(); ie!=ee; ++ie )
45          cout << *ie << " ";
46        cout << ")" << endl;
47      }
48      if( !writer.fileName().empty() )
49        writer.write( graph );
50    }
51  catch( user_interruption & )
52    {
53    }
54  catch( exception & e )
55    {
56      cerr << e.what() << endl;
57      return 1;
58    }
59}

See also

the example 1

Iteration on relations

Graph::iterator i, e = graph.end();
Vertex::iterator ie, ee;
Vertex *v;
Edge   *edge;
for( i=graph.begin(); i!=e; ++i )
{
  v = *i;
  for( ie=v->begin(), ee=v->end(); ie!=ee; ++ie )
  {
    edge = *ie;
    // do something with edge
  }
}

See also

illustrated in example 1

Iteration on ROIs

This is done using RoiIterator <Graph> and MaskIteratorOf, which are “simplified” iterators for ROI graphs which contain Buckets.

Currently MaskIterator does not work on graphs shaped as label volumes.

RoiIteratorOf <AimsData <T> > however does work on volumes.

Complete example 2 source:

 1#include <aims/getopt/getopt2.h>
 2#include <aims/io/reader.h>
 3#include <aims/graph/graphmanip.h>
 4#include <aims/roi/roiIterator.h>
 5#include <graph/graph/graph.h>
 6
 7using namespace aims;
 8using namespace carto;
 9using namespace std;
10
11int main( int argc, const char** argv )
12{
13  Reader<Graph> reader;
14  AimsApplication	app( argc, argv, "Reads a graph, iterates over ROIs " 
15                             "and voxels in ROIs" );
16
17  app.addOption( reader, "-i", "input graph" );
18
19  try
20    {
21      app.initialize();
22
23      Graph	graph;
24      reader.read( graph );
25      cout << reader.fileName() << " read\n";
26
27      cout << "graph vertices: " << graph.order() << endl;
28      cout << "graph edges   : " << graph.edgesSize() << endl;
29      cout << "graph syntax  : " << graph.getSyntax() << endl;
30
31      RoiIteratorOf<Graph>	roiit( graph );
32      // iterate on regions
33      for( ; roiit.isValid(); roiit.next() )
34        {
35          cout << "ROI: " << roiit.regionName() << endl;
36          rc_ptr<MaskIterator>	maskit = roiit.maskIterator();
37          // iterate on voxels
38          for( ; maskit->isValid(); maskit->next() )
39            {
40              cout <<  maskit->value() << "  ";
41            }
42          cout << endl;
43        }
44    }
45  catch( user_interruption & )
46    {
47    }
48  catch( exception & e )
49    {
50      cerr << e.what() << endl;
51      return EXIT_FAILURE;
52    }
53  return EXIT_SUCCESS;
54}

See also

example 2 source file

Access to AIMS objects (meshes, buckets…) in graphs

Using GraphManip class and GraphElementCode structures.

  • When you know what you are looking for in a graph:

     1#include <aims/getopt/getopt2.h>
     2#include <aims/io/reader.h>
     3#include <aims/graph/graphmanip.h>
     4#include <graph/graph/graph.h>
     5
     6using namespace aims;
     7using namespace carto;
     8using namespace std;
     9
    10int main( int argc, const char** argv )
    11{
    12  Reader<Graph>			reader;
    13  Writer<Graph>			writer;
    14  Reader<AimsSurfaceTriangle>	meshreader;
    15  string			labeltoset;
    16  AimsApplication	app( argc, argv, "Reads a graph, finds some selected " 
    17                             "AIMS objects in it, fills other ones" );
    18
    19  app.addOption( reader, "-i", "input graph" );
    20  app.addOption( writer, "-o", "output graph", true );
    21  app.addOption( meshreader, "-m", "mesh to store in selected node", true );
    22  app.addOption( labeltoset, "-n", "name (label) of the selected node to put " 
    23                 "mesh in (if any)", true );
    24
    25  try
    26    {
    27      app.initialize();
    28
    29      // read graph
    30      Graph	graph;
    31      reader.read( graph );
    32      cout << reader.fileName() << " read\n";
    33
    34      // read mesh if any, in a rc_ptr
    35      rc_ptr<AimsSurfaceTriangle>	mesh;
    36      if( !meshreader.fileName().empty() )
    37        mesh.reset( meshreader.read() );
    38
    39      cout << "graph vertices: " << graph.order() << endl;
    40      cout << "graph edges   : " << graph.edgesSize() << endl;
    41      cout << "graph syntax  : " << graph.getSyntax() << endl;
    42
    43      if( mesh )
    44        {
    45          cout << "mesh vertices : " << mesh->vertex().size() << endl;
    46          cout << "mesh polygons : " << mesh->polygon().size() << endl;
    47        }
    48
    49      Graph::iterator				iv, ev = graph.end();
    50      Vertex					*v;
    51      string					label;
    52
    53      for( iv=graph.begin(); iv!=ev; ++iv )
    54        {
    55          v = *iv;
    56          rc_ptr<BucketMap<Void> >	bck;
    57          label.clear();
    58          v->getProperty( "name", label );
    59          if( v->getProperty( "aims_roi", bck ) )
    60            {
    61              cout << "vertex " << v << " (" << v->getSyntax() << ")";
    62              if( !label.empty() )
    63                cout << ", named " << label;
    64              cout << " has a bucket of " << (*bck)[0].size() << " voxels in " 
    65                   << "property aims_roi" << endl;
    66            }
    67          if( label == labeltoset && mesh )
    68            {
    69              GraphManip::storeAims( graph, v, "aims_Tmtktri", mesh );
    70              cout << "mesh stored in vertex " << v << " (" << v->getSyntax() 
    71                   << "), named " << label << " in property aims_Tmtktri" 
    72                   << endl;
    73            }
    74        }
    75      if( !writer.fileName().empty() )
    76        writer.write( graph );
    77    }
    78  catch( user_interruption & )
    79    {
    80    }
    81  catch( exception & e )
    82    {
    83      cerr << e.what() << endl;
    84      return EXIT_FAILURE;
    85    }
    86  return EXIT_SUCCESS;
    87}
    

    See also

    example 3 source file

  • exhaustive iteration:

     1#include <aims/getopt/getopt2.h>
     2#include <aims/io/reader.h>
     3#include <aims/graph/graphmanip.h>
     4#include <graph/graph/graph.h>
     5
     6using namespace aims;
     7using namespace carto;
     8using namespace std;
     9
    10int main( int argc, const char** argv )
    11{
    12  Reader<Graph> reader;
    13  AimsApplication	app( argc, argv, "Reads a graph, finds AIMS objects "
    14                             "in it" );
    15
    16  app.addOption( reader, "-i", "input graph" );
    17
    18  try
    19    {
    20      app.initialize();
    21
    22      Graph	graph;
    23      reader.read( graph );
    24      cout << reader.fileName() << " read\n";
    25
    26      cout << "graph vertices: " << graph.order() << endl;
    27      cout << "graph edges   : " << graph.edgesSize() << endl;
    28      cout << "graph syntax  : " << graph.getSyntax() << endl;
    29
    30      rc_ptr<GraphElementTable> get;
    31      graph.getProperty( "aims_objects_table", get );
    32      if( !get )
    33        {
    34          cout << "no AIMS objects in this graph" << endl;
    35          return EXIT_SUCCESS;
    36        }
    37
    38      Graph::iterator				iv, ev = graph.end();
    39      Vertex::iterator				ie, ee;
    40      Vertex					*v;
    41      GraphElementTable::iterator		iget, eget = get->end();
    42      map<string,GraphElementCode>::iterator	igec, egec;
    43
    44      for( iv=graph.begin(); iv!=ev; ++iv )
    45        {
    46          v = *iv;
    47          iget = get->find( v->getSyntax() );
    48          if( iget != eget )
    49            {
    50              map<string, GraphElementCode> & mgec = iget->second;
    51              for( igec=mgec.begin(), egec=mgec.end(); igec!=egec; ++igec )
    52                {
    53                  GraphElementCode	& gec = igec->second;
    54                  if( ( gec.storageType != GraphElementCode::GlobalPacked 
    55                        && v->hasAttribute( gec.attribute ) )
    56                      || ( gec.storageType == GraphElementCode::GlobalPacked 
    57                           && v->hasAttribute( gec.global_index_attribute ) ) )
    58                    {
    59                      cout << "vertex " << v << " (" << v->getSyntax() 
    60                           << ") has in " << gec.attribute 
    61                           << " an object of type " << gec.objectType 
    62                           << " / " << gec.dataType;
    63                      switch( gec.storageType )
    64                        {
    65                        case GraphElementCode::Local:
    66                          cout << ", local storage, filename in prop " 
    67                               << gec.local_file_attribute;
    68                          break;
    69                        case GraphElementCode::Global:
    70                          cout << ", global storage, filename: " 
    71                               << gec.global_filename 
    72                               << ", referenced in graph prop " 
    73                               << gec.global_attribute 
    74                               << ", index prop in vertex: " 
    75                               << gec.global_index_attribute;
    76                          break;
    77                        case GraphElementCode::GlobalPacked:
    78                          cout << ", globalPacked storage, filename: " 
    79                               << gec.global_filename << ", stored in graph "
    80                               << "prop " << gec.global_attribute 
    81                               << ", index prop in vertex: " 
    82                               << gec.global_index_attribute;
    83                          break;
    84                        }
    85                      cout << endl;
    86                    }
    87                  }
    88            }
    89        }
    90    }
    91  catch( user_interruption & )
    92    {
    93    }
    94  catch( exception & e )
    95    {
    96      cerr << e.what() << endl;
    97      return 1;
    98    }
    99}
    

    See also

    example 4 source file

Internal representation: local or global (see files in .data directory)

  • local: one file per object

  • global: one file for all objects with the same category identifier, concatenated using the time dimension

See also

illusteated in example 4

Internal / external representation: buckets or volumes

  • Buckets: one bucket per vertex (or relation), with global or local representation

  • Volumes: one label volume stored in the graph, and an index in each vertex / relation

Reading / writing

The 3rd optional parameter of Reader::read is used to specify if reading of internal AIMS objects in graphs is requested, and which ones:

Reader<Graph> reader( "filename.arg" );
Graph graph;
reader.read( graph, 0, -1 ); // read all (vertices + relations)
reader.read( graph, 0, 0 ); // read nothing
reader.read( graph, 0, 1 ); // read objects in vetices

// or with more general reading options (more modern):
Reader<Graph> reader( "filename.arg" );
Graph graph;
Object options( new Dictionary );
options->setProperty( "subobjectsfilter", -1 ); // read all, 0: rien, 1: vertices
reader.setOptions( options );
reader.read( graph );

Writing: missing objects which were not read are read before the graph is saved back, so that nothing is missing in the written graph.

#include <aims/io/writer.h>
// ...
Writer<Graph> writer( "filename.arg" );
writer.write( graph );

Note

When a graph is re-written in its original directory, but under a different name, it may or may not reuse the same .data directory, depending on the “filename_base” property in the graph. If this property value is “*”, the .data directory will have the same base name as the .arg, so will be duplicated. This is the default behaviour in recent AIMS releases.

Specialized graphs (sigraph)

These implementations are in the sigraph library.

To read a specialized graph, normally it is sufficient to use the Reader object in “factory” mode (assuming appropriate IO plugins are loaded in AIMS): the specialized graph instance is built and returned by the reader:

Reader<Graph> reader( "filename.arg" );
Graph *graph = reader.read();
// graph can be a derived graph, like FGraph or FRGraph.

It is also possible to force the graph type and to use the other mode of the Reader:

Reader<Graph> reader( "filename.arg" );
FGraph graph;
reader.read( graph );

Another specialized use of Graphs: fiber bundles

The fiber bundles are stored on disk in their own format (.bundles, .bundlesdata). They have no dedicated data structure in memory: this format has been designed to be read and written on-the-fly, in stream mode (see BundleProducer, BundleListener and derived classes).

However they can also be read as a graph: this is how they are used in Anatomist. In principle when the connectomist plugin is loaded, Reader <Graph> can read .bundles files.

In bundles graphs nodes, curves or meshes can be found: meshes of segments or triangles.

In Python: pyaims

Handling in Anatomist

Load a graph

Nomenclatures

Viewing meshes / buckets

Labels

Models, sulci recognition annealing

Future evolutions, planed, dreamed, pending…

  • ROI / DOI: link between ROIs and data “textures”

  • Simpler handling of AIMS in graphs, using higher-level layers like RoiIterator / MaskIterator

  • Complete support of formats XML, minf and SQL

  • Unification of concepts of graph and .minf

  • Transparent handling of referentials and transformations

  • … … … …