Using rtfeedme with AFNI
This simulation of realtime
acquisition was written largely for testing purposes. The steps to
use it are:
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.67This 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.
The "terminate dataset" message must be sent as a full-size image of the size that AFNI is expecting. For example, if you are sending 64x64 2D slices of shorts, AFNI reads image data in chunks of 8192 (64x64x2) bytes. The first 30 bytes of this data are used as the termination message; the rest of this data will be ignored. The message string is (without the quotes):
"Et Earello Endorenna utulien!!"This string is defined by the macro COMMAND_MARKER in plug_realtime.c and rtfeedme.c.
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.
- On Linux, the "shmmax" kernel parameter sets the size of the largest shared memory segment. You can examine this value by the command
cat /proc/sys/kernel/shmmax- On Solaris, the command
/usr/bin/sysdef | grep -i SHMwill show the shared memory parameters. They can be altered by editing the file /etc/system and rebooting, but I don't know the details.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.
- You are probably wondering why the default action is to delete the shared memory segment immediately. The reason is that shared memory segments are perfectly capable of living on with no processes attached to them. Thus, if the image source and AFNI both crash, you'd be left with a chunk of your memory being locked up. Therefore, my original policy was to delete the shared memory segment as soon as it wasn't needed. However, this can lead to annoying error messages. Thus, the introduction of IOCHAN_DELAY_RMID. (BTW, RMID is the shmctl() function code for removing a shared memory segment.)
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.
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):
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.
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 64The 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.
arg = 2D+z -> a single 3D volume, one slice at a time 2D+zt -> multiple 3D volumes, one slice at a time [the default] 3D -> a single 3D volume, all at once 3D+t -> multiple 3D volumes, one full volume at a time-- This command is not required, since there is a default.
I = inferior S = superior A = anterior P = posterior R = right L = left-- This command is optional - if not given, then the volume will be centered about z=0 (which is also the default for the x- and y-axes). If the direction code 'd' is given, then it must agree with the sense of the z-axis given in the XYZAXES command (e.g., if z-axis is I-S, the 'd' must be 'I' or 'S'). When more than one dataset is being acquired in a scanning session, then getting ZFIRST correct is important so that the AFNI datasets will be properly positioned relative to each other (e.g., so you can overlay SPGR and EPI data correctly).
XYZAXES S-I A-P L-R XYZFIRST 30 20A 50Rsets
nx = number of pixels along x-axis ny = number of pixels along y-axis nz = number of pixels along z-axis (optional here)-- This command is required. If 'nz' is not given here, then it must be given using the ZNUM command.
typ = short -> 16 bit signed integers [the default] float -> 32 bit IEEE floats byte -> 8 bit unsigned integers complex -> 64 bit IEEE complex values (real/imag pairs)-- This command is not required, as long as the data are really shorts. The amount of data read for each image will be determined by this command, the XYMATRIX dimensions, and the ACQUISITION_TYPE (whether 2D or 3D data is being sent).
arg = alt -> alternating order (e.g., slices are presented to AFNI in order 1 3 5 7 9 2 4 6 8, when nz=9). = seq -> sequential order (e.g., slices are presented to AFNI in order 1 2 3 4 5 6 7 8 9, when nz=9).-- This command is not required, since 'alt' is the default. It will be ignored if a 3D ACQUISITION_TYPE is used.
I-S (or IS) -> inferior-to-superior S-I (or SI) -> superior-to-inferior A-P (or AP) -> anterior-to-posterior P-A (or PA) -> posterior-to-anterior R-L (or RL) -> right-to-left L-R (or LR) -> left-to-rightFor example, "XYZAXES S-I A-P L-R" specifies a sagittal set of slices, with the slice acquisition order being left-to-right. (In this example, if ZFIRST is used, the 'd' code in that command must be either 'L' or 'R'.) The 3 different axes codes must point in different spatial directions (e.g., you can't say "XYZAXES S-I A-P I-S").
image #1 -> datataset #1 image #2 -> datataset #2 image #3 -> datataset #3 image #4 -> datataset #1 image #5 -> datataset #2 et cetera
DRIVE_AFNI SYSTEM mkdir aaa DRIVE_AFNI CHDIR aaa-- This command is optional.
rtfeedme -dt 300 dataset+origThis 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.