aimsdata  5.0.5
Neuroimaging data handling
AIMS input / output system

There are 3 different layers of IO in Aims for virtually all objects:

  • low-level readers / writers for each specific file format
  • mid-level IO encapsulate all formats supported by Aims for a given object / data type (ie read an AimsData<short> whatever the disk file format): aims::Reader and aims::Writer template classes
  • the highest level readers allow to switch to different pieces of code dealing with each object and data type (ie read an AimsData<short> or a BucketMap<Void>): the aims::Process class

Normal programs perform IO at mid or high level, never at low-level: the lower layer is reserved to the specific usage of the mid-layer Reader and Writer classes for their internal purpose

Mid-level IO: What to do to read a volume of short data ?

High-level IO: My program has to read a file but doesn't know what's in it

Adding new IO formats

Mid-level IO: What to do to read a volume of short data ?

#include <cstdlib>
#include <aims/io/reader.h>
#include <aims/data/data.h>
#include <stdexcept>
using namespace aims;
using namespace std;
try
{
Reader<AimsData<short> > r( "filename" );
r.read( data );
}
catch( exception & e ) // the file couldn't be read
{
cerr << e.what() << endl;
}

You may use an aims::Finder to read the file header and help you determine what's in the file:

#include <aims/io/finder.h>
Finder f;
if( f.check( "filename" ) )
{
if( f.objectType() == "Volume" )
{
if( f.dataType() == "S16" )
{
// ... (call previous function with Reader<AimsData<short> > )
}
else
...
}
else
...
}

High-level IO: My program has to read a file but doesn't know what's in it

Use a aims::Process class, you will avoid using a Finder by yourself and testing all possible types by hand: Process does it for you.

#include <aims/io/reader.h>
#include <aims/data/data.h>
#include <stdexcept>
using namespace aims;
using namespace std;
void myAimsData_short_function( Process &, const string &filename, Finder & )
{
// here I'm sure the file filename contains an AimsData<short>
Reader<AimsData<short> > r( filename );
r.read( data );
// ...
}
void myBucket_void_function( Process &, const string &filename, Finder & )
{
// here I'm sure the file filename contains a bucket<Void>
// (either AimsBucket<Void> or BucketMap<Void>, both use the same files)
Reader<BucketMap<Void> > r( filename );
r.read( data );
// ...
}
int main( int argc, char** argv )
{
string filename = argv[1];
p.registerProcessType( "Volume", "S16", &myAimsData_short_function );
p.registerProcessType( "Bucket", "VOID", &myBucket_void_function );
if( p.execute( filename ) )
return EXIT_SUCCESS;
return EXIT_FAILURE;
}

It's possible to use template functions as callbacks: see aims::Process documentation for details

Adding new IO formats

It's the most complex part since there are several operations to perform. Here you have to know a little more of the mechanisms of Aims IO systems:

  • A header system is responsible for reading the beginning of the file (or the header file): this is the aims::Finder part, which purpose is to determine what's in the input file: object type (volume, bucket, mesh etc), data type (short: "S16", float: "FLOAT"...) and other information which may depend on the objet/data type and/or the format.
  • A reader system (aims::Reader) which actually reads the file and fills an object with it. To do so the object must be already existing, ie you must know its type.
  • Finder
  • Reader / Writer
  • Operations to add an IO format

Finder

A aims::Finder object checks a file header. It uses a list of header reader classes and tries them one after another until a header reader succeeds in reading the file header. There is one header reader for each file format: aims::FinderFormat classes which may in turn use a lower level Header class. This Header object may contain dynamic attributes (see aims::PythonHeader) and is accessible via the Finder after it has been read

Reader / Writer

aims::Reader is a template for each object/data type. Its role is to read a data file into an object in a format-independant way. To do so, Reader has a list of specific format readers which can be tried one after another until one of them "understands" the file and is able to read it. These readers/writers are subclasses of aims::FileFormat and are generally template classes (because there must be one for each data type). These FileFormat objects are stored in a aims::FileFormatDictionary object and shared wy both Reader and Writer classes. There is one FileFormatDictionary for each object/data type (it's a template like Reader and Writer)

Operations to add an IO format

In the most general case these operations are:

But:

  • if you're adding a new format for already supported objects (ie your own volume format to be read as AimsData), aims::Reader, aims::Writer and aims::FileFormatDictionary classes are already compiled on the needed types:
    • write a header class with read the file header
    • wrap it in a aims::FinderFormat inherited class
    • register the FinderFormat in the Finder class
    • define the specific format reader and writer classes
    • wrap them in a (a set of) aims::FileFormat inherited class(es)
    • register them in already defined (and compiled) aims::FileFormatDictionary instances: don't specialize the registerBaseFormats() function (because it's already compiled in the library), but use the static registerFormat() function
  • if you want to compile and use existing IO formats for new data types:
    • (nothing to do for the Finder part)
    • specialize the aims::FinderFormatDictionary::registerBaseFormats() functions and compile them
    • compile (instantiate) the specific aims::Reader and aims::Writer on your type(s)

todo: code examples...