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:
#include "mrilib.h"(which pulls in essentially the whole AFNI C API);opens one or more datasets with
THD_open_dataset()and loads their voxel data withDSET_load();does some computation, usually voxel-wise or volume-wise;
builds an output
THD_3dim_datasetand writes it withDSET_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 |
|---|---|
|
|
|
pulled in by |
|
raw NIML I/O (already available through mrilib) |
|
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 aftermainENTRY, so that-h_view,-h_weband 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.logmechanism); 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 returnsvalue.EXRETURNis thevoidform (pop andreturn;).mainENTRYis themain()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 |
|---|---|
|
informational ( |
|
continuation of a previous INFO line |
|
warning ( |
|
error ( |
|
print error and |
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_spxetc. and routes them to your help function. Do not parse-helpyourself.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.cand make sure it links againstlibmri.a(the library that contains all theTHD_*/mri_*/EDIT_*/ NIML code). TheMakefile.INCLUDEtargets show the standard link line used by the3d*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
-helpoutput, 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
What to read next¶
Datasets and Volumes – the
THD_3dim_dataset, sub-bricks, brick factors, geometry, masks, and creating/writing volumes.Surfaces – SUMA surface objects and surface-based datasets.
Communication: NIML and Live Data – NIML, and the real-time / InstaCorr / GroupInCorr machinery for talking to the AFNI and SUMA GUIs.
The Function Cookbook – a categorized reference of the functions you will reach for most: dataset I/O, time-series extraction, correlation, p-value conversion, and 1D I/O.