16.1.1. Anatomy of an AFNI Program

The 30-second version

Almost every AFNI command-line program is a single .c file in src/ that:

  1. #include "mrilib.h" (which pulls in essentially the whole AFNI C API);

  2. opens one or more datasets with THD_open_dataset() and loads their voxel data with DSET_load();

  3. does some computation, usually voxel-wise or volume-wise;

  4. builds an output THD_3dim_dataset and writes it with DSET_write().

The canonical, heavily commented example is src/3dToyProg.c. If you read only one file before writing your own program, read that one. This chapter describes the scaffolding that every program shares; the following chapters describe the datasets, surfaces, communication, and utility functions that go inside it.

The one header to rule them all

#include "mrilib.h"

mrilib.h is the master header for the mri / THD (the “3D dataset”) world. It transitively includes 3ddata.h (dataset structures and the hundreds of DSET_* / THD_* / EDIT_* declarations), mcw_malloc.h, the NIML headers, the vector/matrix helpers, and the error-message macros. You rarely need to include anything else for a volume-processing program. A few common extras:

Header

When you need it

matrix.h

matrix / vector linear-algebra type

editvol.h

pulled in by mrilib.h; EDIT_* dataset editing

niml.h (via niml/)

raw NIML I/O (already available through mrilib)

suma_suma.h

SUMA surface objects and surface datasets

The main() skeleton

Here is the shape of main() shared by nearly every program, distilled from src/3dToyProg.c:

int main( int argc , char *argv[] )
{
   THD_3dim_dataset *iset=NULL ;
   char *prefix = "toy" ;
   int iarg=1 ;

   mainENTRY("3dToyProg main"); machdep(); AFNI_logger("3dToyProg",argc,argv);

   /*-- parse options --*/
   while( iarg < argc && argv[iarg][0] == '-' ){
      CHECK_HELP(argv[iarg], help_3dToyProg);   /* handles -help/-h/-h_view... */

      if( strcmp(argv[iarg],"-input") == 0 ){
         if( ++iarg >= argc ) ERROR_exit("Need dset after -input") ;
         iset = THD_open_dataset( argv[iarg] ) ;
         if( iset == NULL ) ERROR_exit("Cannot open %s", argv[iarg]) ;
         DSET_mallocize(iset); DSET_load(iset);  /* read voxel data */
         iarg++ ; continue ;
      }

      if( strncmp(argv[iarg],"-prefix",6) == 0 ){
         if( ++iarg >= argc ) ERROR_exit("Need name after -prefix") ;
         prefix = argv[iarg] ; iarg++ ; continue ;
      }

      ERROR_message("ILLEGAL option: %s", argv[iarg]) ;
      suggest_best_prog_option(argv[0], argv[iarg]) ;   /* "did you mean...?" */
      exit(1) ;
   }

   if( argc < 2 ){ help_3dToyProg(TXT,0); exit(0); }  /* no args: print help */
   if( iset == NULL ) ERROR_exit("No input dataset!?") ;

   /* ... do work, build oset ... */

   tross_Copy_History( iset , oset ) ;                 /* provenance */
   tross_Make_History( "3dToyProg", argc, argv, oset ) ;
   DSET_write(oset) ;
   exit(0) ;
}

The mandatory first line

mainENTRY("3dProgName main"); machdep(); AFNI_logger("3dProgName",argc,argv);
  • mainENTRY(name) sets up the AFNI call-stack traceback machinery and installs signal handlers so that a crash prints a readable stack instead of a bare segfault. All argument parsing must happen after mainENTRY, so that -h_view, -h_web and friends work correctly (see How to Format Help Strings).

  • machdep() performs machine-dependent setup (byte-order, floating-point environment, etc.).

  • AFNI_logger(name,argc,argv) records that the program was run (the ~/.afni.log mechanism); harmless and conventional to include.

ENTRY / RETURN: the call-stack traceback

Throughout the AFNI source you will see functions that begin with ENTRY and leave through RETURN instead of the bare return:

THD_3dim_dataset * New_Dataset_From_Scratch(char *prefix)
{
   THD_3dim_dataset *oset=NULL ;
   ENTRY("New_Dataset_From_Scratch") ;    /* register this frame */
   ...
   RETURN(oset) ;                          /* pop frame + return value */
}
  • ENTRY("name") pushes the function name onto a debug stack.

  • RETURN(value) pops it and returns value.

  • EXRETURN is the void form (pop and return;).

  • mainENTRY is the main() variant.

The payoff is that when something goes wrong, AFNI can print the whole chain of functions that were active (DBG_traceback / the -DAFNI_TRACE=y environment control). Using them is optional for code that never fails, but matching the surrounding style is strongly encouraged: if you ENTRY at the top you must leave through RETURN/EXRETURN on every path, or the stack gets out of sync.

Talking to the user: message macros

Do not call printf/fprintf(stderr,...) directly for status and errors. AFNI has a standard family (declared in mrilib.h) that adds the ++ / *+ / ** prefixes users recognize, honors quiet flags, and colorizes terminals:

Macro

Meaning

INFO_message(fmt,...)

informational (++), to stderr

ININFO_message(fmt,...)

continuation of a previous INFO line

WARNING_message(fmt,...)

warning (*+)

ERROR_message(fmt,...)

error (**) but keep running

ERROR_exit(fmt,...)

print error and exit(1) – the usual fatal path

Use ERROR_exit for fatal command-line problems (missing dataset, bad option) and ERROR_message when you want to report but continue. This is exactly what src/3dToyProg.c does when it fails to open the -mask or -input dataset.

Help, option-suggestion, and tab-completion

Two calls in the skeleton above give you AFNI’s user-friendliness for free:

  • CHECK_HELP(arg, help_func) intercepts -h, -help, -HELP, -h_view, -h_web, -h_spx etc. and routes them to your help function. Do not parse -help yourself.

  • suggest_best_prog_option(argv[0], arg) prints a “did you mean …?” suggestion when the user gives an unknown option.

Your help function has the prototype int help_prog(TFORM targ, int detail) and prints with sphinx_printf(targ, ...) so the same text can render as plain text or as Sphinx for these web docs. The full mechanics – including how to register your program’s options in prog_opts.c for tab-completion – are documented in How to Format Help Strings. Program src/3dToyProg.c is the reference implementation.

Provenance: dataset history

Before writing any output dataset, stamp it so the analysis is reproducible:

tross_Copy_History( iset , oset ) ;                  /* carry input's log */
tross_Make_History( "3dProgName", argc, argv, oset ) ;/* append this command */

tross_Make_History records the exact command line into the dataset header; 3dinfo -history will later show it. Copying the input history first preserves the full chain back to the raw data. Every well-behaved AFNI program does this (grep the tree for tross_Make_History – it is nearly universal).

Overwrite protection

AFNI programs refuse to clobber an existing dataset unless the user passed -overwrite (or set AFNI_DECONFLICT). The idiom, straight from src/3dToyProg.c:

if( !THD_ok_overwrite() && THD_is_ondisk(DSET_HEADNAME(oset)) )
   ERROR_message("Output %s already exists", DSET_HEADNAME(oset)) ;
else
   DSET_write(oset) ;

Building your program into AFNI

New programs are compiled by the src/Makefile machinery. In practice:

  • Add your prog.c and make sure it links against libmri.a (the library that contains all the THD_* / mri_* / EDIT_* / NIML code). The Makefile.INCLUDE targets show the standard link line used by the 3d* programs; copy an existing program’s target as a template.

  • For the CMake build, see the CMake build docs in the developer documentation.

  • After adding or updating your -help output, regenerate the option-completion table as described in prog_opts:

    apsearch -C_prog_opt_array 3dProgName > prog_opts.c
    touch thd_getpathprogs.c
    make libmri.a gitignore