How to Program Realtime AFNI

The AFNI realtime plugin will assemble reconstructed slices or volumes into a 3D or 3D+time dataset. It does not do image reconstruction itself, but requires an image source. This is the documentation on how to write such an image source program.

Sample Program

The program rtfeedme.c (part of the AFNI source code distribution) will take a pre-existing AFNI dataset, take it apart, and transmit it to the realtime plugin for reassembly and other processing. This software can be used as a starting point for development of a new program.

Using rtfeedme with AFNI
This simulation of realtime acquisition was written largely for testing purposes. The steps to use it are:

  1. Start AFNI with the command
        afni -rt
    (the -rt switch enables the realtime plugin).

  2. Use the Datamode->Plugins menu to start the RT Options plugin, which lets you control how various things happen during realtime acquisition.

  3. Choose an existing dataset (say fred+orig), and send it to AFNI with the command
        rfteedme -dt 30 fred+orig

rtfeedme is not compiled by default with the rest of the AFNI package; if you have acquired the source code package, then you can compile this program with the command   make rtfeedme .


Hosts that AFNI Trusts

If you want to try using rtfeedme from a different system, and send data across the network, the command to do this would be
    rfteedme -dt 30 -hostname afnicomputername fred+orig
However, AFNI won't accept a TCP/IP connection from just anyone.

AFNI checks the incoming IP address of socket connections to see if the host is on the "trust list". The default trust list is

   127.0.0.1 = localhost
   192.168   = private class B networks (this is a reserved set of
               addresses that should not be visible to the Internet)
You can add to this list by defining the environment variable as in the example below (before starting AFNI):
   setenv AFNI_TRUSTHOST 123.45.67
This means that any IP address starting with the above string will be acceptable. If you want to add more than one possibility, then you can also use environment variables AFNI_TRUSTHOST_1, AFNI_TRUSTHOST_2, up to AFNI_TRUSTHOST_99. (That should be enough - how trusting do you really want to be?) You can also use hostnames (but not network names) in a AFNI_TRUSTHOST environment variable, but the IP address method is surer to work.

A number of other environment variables can control how the realtime plugin operates. See the text file README.environment for the details.


Structure of a Realtime Image Source

There are several steps that an image source must take to get the communications with AFNI established:
  1. Open the Control Channel
    The only information that is sent down the control channel is the description of how the actual data will be transmitted (i.e., describe the data channel). After this is done, the control channel should be closed by the image source.

  2. Open the Data Channel
    This is the communication pathway that will be used to send image data and metadata from the image source program to AFNI.

  3. Send Image Metadata
    This information describes the format of the images that are about to be sent.

  4. Send Image Data
    This is done as much as desired. When the data channel is broken, AFNI will write the new dataset to disk.


Communicating with AFNI

IOCHANs
An external program talks to the realtime plugin using an abstract communication device called an "IOCHAN". An IOCHAN can either be a TCP/IP socket or a shared memory segment; the former would be used for communicating between computers and the latter for communicating within a single system. These routines are implemented in the files thd_iochan.[ch], which are compiled into libmri.a.

An IOCHAN is opened with the function iochan_init, which takes two arguments. The first argument is a string that specifies what kind of channel is desired (TCP/IP or shared memory), and also specifies other information about the channel. The second argument is the string "w" for an IOCHAN that is intended to be mostly written by the caller, or is the string "r" for one that is intended to be mostly read by the caller. An image source program should always use "w", since it is sending data to AFNI.

The format of an IOCHAN specification for a TCP/IP socket is: "tcp:hostname:portnumber", where hostname is the computer name (or IP address in dot format) with which you wish to communicate, and portnumber is the TCP/IP "port number" that should be opened. For example, the realtime plugin control port number is 7954, so that to open a socket IOCHAN to the realtime plugin running on the current host, the call might be

  IOCHAN * ioc ;
  ioc = iochan_init( "tcp:localhost:7954" , "w" ) ;
  if( ioc == NULL ) fprintf(stderr,"** Failure to open control channel\n");
In this example, the IOCHAN struct pointed to by ioc will have a socket that is created using the Unix system call connect, which will reach out to the specified host to make the connection. If "r" were used for the second argument, the program would use the Unix system call accept to wait for another program to try to connect.

The format of an IOCHAN specification for a shared memory segment is: "shm:name:size", where name is a symbolic name to be attached to the memory segment (used to distinguish it from other shared memory segments) and size is the size of the memory segment in bytes. This size determines the amount of data that can be transferred without blocking, and should be at least twice the size of a single image that will be transferred at one time. For example:

  IOCHAN * ioc ;
  ioc = iochan_init( "shm:Elvis:2M" , "w" ) ;
  if( ioc == NULL ) fprintf(stderr,"** Failure to open control channel\n");
In this example, size = 2M means a 2 megabyte buffer is used. If the "M" were left off, it would mean a 2 byte buffer, which would be rather useless. (You can also use the suffix "K" to indicate the buffer size in Kilobytes.)

shm Note #1: All operating systems that I know of put a limit on the size of a shared memory segment. On some systems, this limit is fairly small. You may have to increase this limit in order to use shm: IOCHANs as large as the one in the example above. In all cases, changing this limit can only be done by the superuser, and depends on the Unix platform you are using.

shm Note #2: Another operating system dependent issue is whether the function call that deletes a shared memory segment destroys it immediately, or only marks it for destruction, with the actual destruction only occurring when all other processes detach from the segment. This makes a difference in AFNI, since the normal way for a program to close a shm: IOCHAN is to delete the shared memory segment. However, if this causes the immediate destruction of the segment, then the other process attached to the IOCHAN will print out some error messages, complaining that the shared memory buffer has gone bad. There

To avoid such messages, set the environment variable IOCHAN_DELAY_RMID to the string YES. Then the shm: IOCHAN memory buffer will only be deleted when the last process detaches from it.

For AFNI, you can set IOCHAN_DELAY_RMID in your .afnirc file (cf. README.environment). However, this won't affect the image source program, so you should probably set it explicitly in your .cshrc file (or equivalent), if needed.

On most Unix systems, the ipcs -m command will show you the shared memory segments that currently exist. If something is left dangling (with 0 processes attached) from an AFNI run, you might be able to delete it with the ipcrm command. But be careful - you don't want to delete a shared memory segment from some other program! (For example, KDE uses shared memory to communicate.)

If you are writing your own image source program, you can look at rtfeedme.c to see how to ensure that IOCHANs are closed when the program exits, even if it exits because of a crash condition (e.g., segfault). This goal is accomplished by creating a signal handler to catch fatal errors; the signal handler function then closes down any open IOCHANs before it actually exits. This technique is a way to prevent dangling shared memory segments. (Chicken soup might help, too.)

A TCP/IP IOCHAN can be used bi-directionally, once both processes have attached to it. This is not true of the shared memory IOCHAN shown above: only the writing process can write to it, and only the reading process can read from it. To avoid this difficulty, a second form of shared memory IOCHAN can be created with a specification of the form "shm:name:size1+size2". Here, size1 is the size of the buffer that will be used by the "w" process to write into and by the "r" process to read from; size2 is the size of the buffer that will be used by the "r" process to write into and by the "w" process to read from. These sizes can be different, which might be useful if more data is expected to flow one way than another.

After an IOCHAN is created, it is not necessarily ready to be used. Until the other process (AFNI in this case) attaches to the IOCHAN, no data can be sent. A number of functions are available to check if an IOCHAN is in a usable state. They are documented in the comments at the head of thd_iochan.c, and examples of their use can be found in rtfeedme.c.

The Structure of rtfeedme.c
This program initiates the conversation with AFNI with the function AFNI_start_io, which will establish the control IOCHAN, send the control information down it, close the control IOCHAN, and then open the data IOCHAN. After that is accomplished, the main program actually sends the image metadata and then finally the images themselves.

AFNI_start_io is designed to allow its caller to perform other functions while it is establishing the communications with AFNI. In rtfeedme.c, the caller (main) doesn't do anything, but just calls AFNI_start_io repeatedly until it signals that it is ready. In the MCW actual realtime image source program, AFNI_start_io is called once every time an echo-planar image is acquired. When the data IOCHAN is finally opened, then the stored images are sent, and after that every time a new image is acquired, it is sent immediately. AFNI_start_io moves through 5 modes, labeled by symbolic constants:

  #define AFNI_OPEN_CONTROL_MODE   1  /* 1st time thru: open control channel */
  #define AFNI_WAIT_CONTROL_MODE   2  /* waiting for AFNI to open control    */
  #define AFNI_OPEN_DATA_MODE      3  /* now can open data channel to AFNI   */
  #define AFNI_CATCHUP_MODE        4  /* waiting for AFNI to open data       */
  #define AFNI_CONTINUE_MODE       5  /* at last! data channel is ready!     */
The mode that the routine is in at any moment is stored in the global variable AFNI_mode, which should be set to AFNI_OPEN_CONTROL_MODE before the first call to AFNI_start_io. The sequence of events is outlined below (you should also read rtfeedme.c to see how this works): At this point, AFNI_start_io is finished, and the actual transmission of image information is the responsibility of the caller.

Realtime Control IOCHAN
When not acquiring data, the realtime plugin is listening on socket 7954, so the image source program starts talking to AFNI with the code (taken from rtfeedme.c):

 #define AFNI_CONTROL_PORT 7954
    char AFNI_iochan[128] , AFNI_host[128] ;
    IOCHAN * AFNI_ioc ;

    sprintf( AFNI_iochan , "tcp:%s:%d" , AFNI_host , AFNI_CONTROL_PORT ) ;
    AFNI_ioc = iochan_init( AFNI_iochan , "w" ) ;
At this point, the image source must wait until the realtime plugin connects to the newly opened socket, as described above.

When the control IOCHAN is ready, the control information should be sent. The control information is a single C NUL-terminated string, which will contain 1 or 2 lines of data. The first line of data is just the string that specifies the data IOCHAN (which can be of shm: or tcp: type).

The second line of control data, if present, specifies a command that realtime plugin should execute to obtain the image metadata. If there is no second line of data (that is, there is no '\n' character in the control string), then all the image metadata must come from the image source program itself. Otherwise, the realtime plugin will execute the metadata command in a child process; it will capture the standard output of this command and use that as (most of) the image metadata.

The reason for this provision is that some aspects of the images may not be known when the image source program starts. For example, at the MCW 3 Tesla system, the image acquisition program does not know the orientation or dimensions of the data. This information must be obtained by querying the MRI console software after the acquisition actually starts. The program 3T_toafni.c is the medium by which this query is made. The response from the MRI console is parsed and converted to AFNI image metadata, and written to stdout. Thus, in the MCW realtime acquisition system, the control information looks something like this:

  "shm:Elvis:1M\n3T_toafni"
Since rtfeedme.c will supply all the image metadata itself through the data IOCHAN, no metadata command is used in this sample program.

Data IOCHAN
When the data IOCHAN is ready, then the image source program must send some image metadata as the first transmission. (The details of the image metadata are discussed in the next section.) If an external metadata command is used to supply this information, then only one metadata command need be supplied. In the MCW 3 Tesla realtime system, this command is generated by

  char mdata[128] ;
  sprintf( mdata , "DATUM short\nXYMATRIX %d %d\n" , nx,ny ) ;
where nx and ny define the array dimensions of the reconstructed slices. (This information is not available from the MRI console, since the reconstruction matrix need not be the same as the k-space acquisition matrix.) After the NUL-terminated metadata command string, all further data that comes down the data IOCHAN is taken to be raw image slice (or volume) data.


Image Metadata Commands

AFNI needs some information about the acquisition in order to properly construct a dataset from the images. This information is sent to AFNI as a series of command strings. A sample set of command strings is given below:
   ACQUISITION_TYPE 2D+zt
   TR 5.0
   XYFOV 240.0 240.0 112.0
   ZNUM 16
   XYZAXES S-I A-P L-R
   DATUM short
   XYMATRIX 64 64
The commands can be given in any order. Each command takes up a single line of input (i.e., commands are separated by the '\n' character in the input buffer, and the whole set of commands is terminated by the usual '\0'). The entire set of commands must be less than 32 Kbytes long (the usual length is about 200 characters). Each command line has one or more arguments. The full list of possible command strings and their arguments is given below.

Recording Results of rtfeedme+AFNI - April 2002

AFNI now has the ability to record the sequence of images as they are computed by the realtime plugin. The following sequence of operations would be needed:
  1. You need a 3D+time AFNI dataset to simulate sending realtime data to the program.
  2. Open 2 terminal windows, and cd to that directory in both of them.
  3. Start AFNI with realtime I/O enabled in one of the terminals with the command "afni -rt".
  4. Now you have to set for realtime analysis and display. To start with, open the Axial Image and Graph windows. In the Graph window, use the FIM menu (lower right corner) to "Pick Ideal", and select some 1D file as the ideal. This should then graph on top of the central sub-graph.
  5. You are now ready to send the data into AFNI for realtime analysis. The command to send the dataset to AFNI via the realtime socket is
      rtfeedme -dt 300 dataset+orig
    This will feed the dataset, 300 ms per slice, to AFNI. Since you selected FIM on the RT Options plugin, you will get a sequence of colored overlays displayed in the Image window. Since Record is turned on, you will also get a new image window popping up that will contain the recorded images. Wait until the data is all sent to AFNI.
  6. You can now save the recorded images to disk. To choose the format, right click on the "Save:bkg" button in the Record window (not the original Image window). You should see at least "Save.ppm". Choose that and press "Set" - then the button should now say "Save.ppm". Press this button with the left mouse click now. You get 3 dialog boxes in order, that ask for "Filename prefix", "Slice from", and "Slice to". This will let you save the images to external files named fred.0000.ppm, et cetera.
A simpler way would be to use a video camera to record the screen as you run the acquisition.