/****************************************************************** Sample plugout program to send AFNI commands that drive the AFNI interface remotely. Current commands [07 Nov 2001] are RESCAN_THIS Q SET_SESSION Q.dirname SET_ANATOMY Q.prefix SET_FUNCTION Q.prefix OPEN_WINDOW Q.windowname CLOSE_WINDOW Q.windowname QUIT where "Q" is the letter for the AFNI controller you are driving (Q=A,B,C,D, or E). "Q." can be omitted, in which case you are driving controller A. Other parameters are: dirname = name of directory that AFNI should switch to (must already be read into AFNI) prefix = prefix part of dataset name that AFNI should switch to (must be in the current session) windowname = axialimage OR coronalimage OR sagittalimage OR axialgraph OR coronalgraph OR sagittalgraph Doing OPEN_WINDOW or CLOSE_WINDOW on just the single letter Q will open or close controller Q itself. However, you cannot use CLOSE_WINDOW to kill AFNI - it won't close the last controller window. See README.driver for up-to-date information on what commands are available for driving Miss AFNI. *******************************************************************/ /***** Header file for communication routines *****/ #include "afni_environ.h" #include "afni_plugout.h" #include "thd_iochan.h" #include "niml.h" #include "cs.h" /***** Global variable determining on which system AFNI runs. *****/ /***** [default is the current system, can be changed by user] *****/ static char afni_host[128] = "localhost" ; static char afni_name[128] = "\0" ; static int afni_port = 0 ; /* Init. before parsing command line ZSS June 2011 */ static int afni_verbose = 0 ; /* print out debug info? */ static int DontWait = 0; static int N_com = 0; static int I_com = 0; /* 22 Mar 2005 - RWCox */ static char *com[1024]; /***** Prototype *****/ /*=================================================================== This routine handles all communications with AFNI. The only input is the global variable afni_host, which determines on which system AFNI is running. The output is -1 if an error occurred, 0 if everything is OK. =====================================================================*/ /***** Mode flags for determining what afni_io does. The routine progress through 5 stages: 1) Open a control connection to AFNI; then 2) Wait until AFNI also opens the control connection; then 3) Send a control string to AFNI saying what kind of information we want, wait for an acknowledgment, close the control connection, and open a data connection to AFNI; then 4) Wait for AFNI to also open the data connection; then 5) Send data to AFNI! *****/ #define AFNI_OPEN_CONTROL_MODE 1 /* 1st time thru: open control channel */ #define AFNI_WAIT_CONTROL_MODE 2 /* wait for AFNI to open control chan */ #define AFNI_OPEN_DATA_MODE 3 /* now can open data channel to AFNI */ #define AFNI_WAIT_DATA_MODE 4 /* waiting for AFNI to open data chan */ #define AFNI_CONTINUE_MODE 5 /* at last! data channel is ready! */ /***** macros to send acknowledgement strings to AFNI *****/ #define POACKSIZE 4 /* length of acknowledgement strings */ #define PO_ACK_BAD(ic) iochan_sendall( (ic) , "BAD" , POACKSIZE ) #define PO_ACK_OK(ic) iochan_sendall( (ic) , "OK!" , POACKSIZE ) #define PO_SEND(ic,str) iochan_sendall( (ic) , (str) , strlen((str))+1 ) static int afni_mode = AFNI_OPEN_CONTROL_MODE ; /* status variable */ static int exit_status = 1 ; /* 1 if never connected, 0 if connected */ static float maxwait = 9.0f ; /* 27 Dec 2010 */ int afni_io(void) { static IOCHAN *afni_ioc = NULL ; /* connection to AFNI */ static float delta_t = -1.0; /* time spent waiting */ float Time_Fact = 1000000.0; /* one meeelllion microseconds */ static struct timeval tw; struct timeval tn; int ii ; /***************************************************************/ /***** Check to see if status is OK before we proceed. *****/ /***** (if an error occurs below, afni_mode gets set to 0) *****/ if( afni_mode <= 0 ) return -1 ; /***********************************************************************/ /***** First time into this routine? Open control channel to AFNI *****/ if( afni_mode == AFNI_OPEN_CONTROL_MODE ){ char afni_iocname[128] ; /* will hold name of I/O channel */ /** Note that the control channel is always a TCP/IP channel to port #get_port_named("AFNI_PLUGOUT_TCP_0") (used to be #7955) on the AFNI host system **/ if( strcmp(afni_host,".") == 0 ) sprintf( afni_iocname , "tcp:%s:%d" , "localhost", get_port_named("AFNI_PLUGOUT_TCP_0") ); /* make name */ else sprintf( afni_iocname , "tcp:%s:%d" , afni_host, get_port_named("AFNI_PLUGOUT_TCP_0") ) ; /* make name */ afni_ioc = iochan_init( afni_iocname , "create" ) ; /* create it */ if( afni_ioc == NULL ){ fprintf(stderr, "** Can't create control channel %s to AFNI!\n",afni_iocname) ; afni_mode = 0 ; return -1 ; } afni_mode = AFNI_WAIT_CONTROL_MODE ; /* waiting for AFNI connection */ gettimeofday(&tw, NULL); /* keep track of time that you began waiting */ if( afni_verbose ) fprintf(stderr,"++ AFNI control channel %s created\n", afni_iocname) ; } /****************************************************/ /**** Check if AFNI control channel is connected ****/ if( afni_mode == AFNI_WAIT_CONTROL_MODE ){ ii = iochan_writecheck( afni_ioc , 5 ) ; /* wait at most 5 msec */ gettimeofday(&tn, NULL); /* what time is it now? */ /** the iochan_*check() routines return -1 for an error, 0 if not ready, >0 if the I/O channel is ready. **/ if( ii < 0 ){ fprintf(stderr,"** Control channel to AFNI failed!\a\n") ; IOCHAN_CLOSENOW(afni_ioc) ; afni_mode = 0 ; return -1 ; } else if( ii > 0 ){ afni_mode = AFNI_OPEN_DATA_MODE ; /* prepare to send data */ if( afni_verbose ) fprintf(stderr,"++ AFNI control channel connected\n"); } else { delta_t = (((float)(tn.tv_sec - tw.tv_sec )*Time_Fact) + /* time spent waiting */ (float)(tn.tv_usec - tw.tv_usec))/Time_Fact ; if (delta_t > maxwait) { fprintf(stderr, "** Waited %g seconds to no avail ==> I quit.\n",maxwait); IOCHAN_CLOSENOW(afni_ioc) ; afni_mode = 0 ; return -1 ; } else { return 0 ; /* try again next time */ } } } /**********************************************************/ /**** Send control data to AFNI, and open data channel ****/ if( afni_mode == AFNI_OPEN_DATA_MODE ){ char afni_iocname[128] ; char afni_buf[256] ; char shmstr[256]; exit_status = 0 ; /** decide name of data channel: use shared memory (shm:) on ".", use TCP/IP (tcp:) on other computer systems; * Note that the TCP/IP port number can be anything that isn't already in use; * Note that the shm control name (here "test_plugout") is a string that will be converted to an IPC key (in function string_to_key in iochan.c). **/ if( strcmp(afni_host,".") == 0 ) { sprintf( shmstr, "shm:test_plugout:%dK+%dK", PLUGOUT_SHM_SIZE_K, PLUGOUT_SHM_SIZE_K); strcpy( afni_iocname , shmstr ) ; } else sprintf( afni_iocname , "tcp:%s:%d" , afni_host , afni_port ) ; /** write the command to AFNI into the buffer: * each command ends with a newline character, except (possibly) the last command; * the command buffer is a C string, which ends with an ASCII NUL character; * PONAME means 'use this string for informative messages'; * IOCHAN means 'use this I/O channel from now on'. **/ if( afni_name[0] == '\0' ) strcpy(afni_name,"plugout_drive") ; sprintf( afni_buf , "PONAME %s\n" "IOCHAN %s" , afni_name , afni_iocname ) ; if( afni_verbose ) fprintf(stderr,"++ Sending control information to AFNI\n %s\n", afni_buf) ; /** note that the ASCII NUL at the end of the buffer is sent **/ ii = iochan_sendall( afni_ioc , afni_buf , strlen(afni_buf)+1 ) ; /** the return value is the number of bytes sent, or -1 indicating a fatal error transpired. **/ if( ii < 0 ){ fprintf(stderr,"** Transmission of control data to AFNI failed!\a\n") ; IOCHAN_CLOSENOW(afni_ioc) ; afni_mode = 0 ; return -1 ; } else { /** wait for the acknowledgment from AFNI, then close channel **/ ii = iochan_recvall( afni_ioc , afni_buf , POACKSIZE ) ; IOCHAN_CLOSENOW(afni_ioc) ; if( ii < 0 || strncmp(afni_buf,"OK!",3) != 0 ){ fprintf(stderr,"** AFNI didn't like control information!\a\n") ; afni_mode = 0 ; return -1 ; } /** now open data channel to AFNI **/ afni_ioc = iochan_init( afni_iocname , "create" ) ; if( afni_ioc == NULL ){ fprintf(stderr, "** Can't open data channel %s to AFNI!\a\n",afni_iocname) ; afni_mode = 0 ; return -1 ; } else { afni_mode = AFNI_WAIT_DATA_MODE ; if( afni_verbose ) fprintf(stderr,"++ AFNI data channel created\n") ; } } } /****************************************************/ /***** See if data channel is connected to AFNI *****/ if( afni_mode == AFNI_WAIT_DATA_MODE ){ ii = iochan_goodcheck( afni_ioc , 5 ) ; /* wait at most 5 msec */ if( ii < 0 ){ fprintf(stderr, "** AFNI data channel aborted before any data was sent!\a\n") ; IOCHAN_CLOSENOW( afni_ioc ) ; afni_mode = 0 ; return -1 ; } else if( ii > 0 ){ /* ready to go! */ afni_mode = AFNI_CONTINUE_MODE ; if( afni_verbose ) fprintf(stderr,"++ AFNI data channel is open\n") ; } else { return 0 ; /* try again next time */ } } /**************************************************************/ /***** The "normal" state of affairs: AFNI is connected. *****/ /***** See if the user wants to drive AFNI. *****/ if( afni_mode == AFNI_CONTINUE_MODE ){ char cmd_buf[PLUGOUT_COM_LENGTH] , afni_buf[PLUGOUT_COM_LENGTH+56]; if( I_com < N_com ){ /* send the I_com'th command */ strcpy(afni_buf, "DRIVE_AFNI ") ; strcat(afni_buf, com[I_com] ) ; strcpy(cmd_buf,com[I_com]) ; if (afni_verbose) { fprintf(stderr,"Command String %d Echo: '%s'\n", I_com, afni_buf); fflush(stderr) ; } I_com++ ; } else { char *qpt ; if (DontWait) exit(0); /* get user input */ printf("Enter command: ") ; fflush(stdout) ; qpt = afni_fgets(cmd_buf,PLUGOUT_COM_LENGTH,stdin) ; if( qpt == NULL ) exit(0) ; /* 18 Mar 2007 */ /* make command to AFNI */ strcpy(afni_buf,"DRIVE_AFNI ") ; strcat(afni_buf,cmd_buf) ; } /* send command to AFNI */ ii = iochan_sendall( afni_ioc , afni_buf , strlen(afni_buf)+1 ) ; if( strcmp(cmd_buf,"QUIT") == 0 ){ /* 28 Jul 2005 */ iochan_sleep(222) ; exit(0) ; } if( ii > 0 ){ /* send was OK; wait for acknowledgment */ ii = iochan_recvall( afni_ioc , afni_buf , POACKSIZE ) ; if( ii > 0 && afni_verbose ) printf("++ AFNI response string: %s\n",afni_buf) ; } if( ii < 0 ){ /* send or acknowledgment failed */ fprintf(stderr,"** AFNI data channel aborted!\a\n") ; IOCHAN_CLOSENOW(afni_ioc) ; afni_mode = 0 ; return -1 ; } return 0 ; } return 0 ; } extern char *SUMA_Offset_SLines(char *, int); void usage_plugout_drive(int detail) { printf("\n" "Usage: plugout_drive [-host name] [-v]\n\n" "This program connects to AFNI and sends commands\n" " that the user specifies interactively or on command line\n" " over to AFNI to be executed.\n" "\n" "NOTE:\n" " If you quit plugout_drive and then re-start it immediately\n" " (as in a script), you might run into problems re-connecting\n" " to AFNI. The reason is that the TCP/IP system doesn't hang\n" " up a socket instantly when commanded to do so; the socket\n" " takes about a second to close down completely. If you are\n" " writing a script that starts plugout_drive repeatedly, you\n" " should insert a command 'sleep 1' between each start, to\n" " give the operating system time to clean the socket up.\n" " Otherwise, AFNI might not be able to open the socket,\n" " and plugout_drive will output an error message:\n" " ** AFNI didn't like control information!\n" "\n" "OPTIONS:\n" " -host name Means to connect to AFNI running on the computer\n" " 'name' using TCP/IP. The default is to connect\n" " on the current host 'localhost' using TCP/IP.\n" "\n" " -shm Means to connect to the current host using shared\n" " memory. There is no reason to do this unless\n" " you are transferring huge quantities of data.\n" " N.B.: '-host .' is equivalent to '-shm'.\n" "\n" " -v Verbose mode.\n" "\n" " -port pp Use TCP/IP port number 'pp'. The default is\n" " %d, but if two plugouts are running on the\n" " same computer, they must use different ports.\n" " For a list of currently used ports use afni -list_ports\n" "\n" " -maxwait t Wait a maximum of 't' seconds for AFNI to connect;\n" " if the connection doesn't happen in that time, exit.\n" " [default wait time is 9 seconds]\n" "\n" " -name sss Use the string 'sss' for the name that AFNI assigns\n" " to this plugout. The default is something stupid.\n" "\n" " -com 'ACTION DATA' Execute the following command. For example:\n" " -com 'SET_FUNCTION SomeFunction'\n" " will switch AFNI's function (overlay) to\n" " dataset with prefix SomeFunction. \n" " Make sure ACTION and DATA are together enclosed\n" " in one pair of single quotes.\n" " There are numerous actions listed in AFNI's\n" " README.driver file.\n" " You can use the option -com repeatedly. \n" "\n" " -quit Quit after you are done with all the -com commands.\n" " The default is for the program to wait for more\n" " commands to be typed at the terminal's prompt.\n" "\n" "NOTES:\n" "You will need to turn plugouts on in AFNI using one of the\n" "following methods: \n" " 1. Including '-yesplugouts' as an option on AFNI's command line\n" " 2. From AFNI GUI: Define Datamode->Misc->Start Plugouts\n" " 3. From AFNI GUI: Press the 'NIML+PO' button (near 'Overlay')\n" " 4. Set environment variable AFNI_YESPLUGOUTS to YES in .afnirc\n" "Otherwise, AFNI won't be listening for a plugout connection.\n" " [AFNI doesn't listen for socket connections, unless]\n" " [it is told to, in order to avoid the overhead of]\n" " [checking for incoming data every few milliseconds]\n" "\n" "This program's exit status will be 1 if it couldn't connect\n" "to AFNI at all. Otherwise, the exit status will be 0.\n" "You could use this feature in a script to check if a copy of\n" "AFNI is ready to rumble, and if not then start one, as in the\n" "following csh fragment:\n" " plugout_drive -maxwait 1 -com 'OPEN_WINDOW axialimage'\n" " if( $status == 1 )then\n" " afni -yesplugouts &\n" " sleep 2 ; plugout_drive -com 'OPEN_WINDOW axialimage'\n" " endif\n" "\n" "To have different plugout_* programs talking to different\n" "AFNI, use the -np* options below\n" "%s\n" "Global Options (available to all AFNI/SUMA programs)\n" "%s\n%s" "Example 1:\n" " afni -yesplugouts\n" " plugout_drive -com 'SWITCH_SESSION A.afni' \\\n" " -com 'OPEN_WINDOW A.axialimage geom=600x600+416+44 \\\n" " ifrac=0.8 opacity=9' \\\n" " -com 'OPEN_WINDOW A.sagittalimage geom=+45+430 \\\n" " ifrac=0.8 opacity=9' \\\n" " -com 'SWITCH_UNDERLAY anat' \\\n" " -com 'SWITCH_OVERLAY strip' \\\n" " -com 'SEE_OVERLAY +' \\\n" " -com 'SET_DICOM_XYZ 7 12 2' \\\n" " -com 'OPEN_WINDOW A.axialimage keypress=v' \\\n" " -quit \n" "\n" "More help in: README.driver\n" "More Demos is: @DriveAfni\n" "\n" , afni_port, get_np_help(), SUMA_Offset_SLines(get_help_help(),2), get_gopt_help() ); return; } /*=================================================================== Main program: Read command line for options Call afni_io routine forever =====================================================================*/ int main( int argc , char *argv[] ) { int narg , ii; (void)AFNI_prefilter_args(&argc,&argv); afni_port = get_port_named("PLUGOUT_DRIVE_PORT"); /* ZSS June 2011 */ /***** See if the pitiful user wants help *****/ if( argc == 2 && ( strcasecmp(argv[1],"-help") == 0 || strcasecmp(argv[1],"-h") == 0) ){ usage_plugout_drive(strlen(argv[1]) > 3 ? 2:1); exit (0); } /***** Process command line options *****/ N_com = 0; DontWait = 0; narg = 1 ; while( narg < argc ){ /** -maxwait [27 Dec 2010] **/ if( strcmp(argv[narg],"-maxwait") == 0 ){ if( ++narg >= argc ){ fprintf(stderr,"** -maxwait needs a following number!\a\n"); exit(1); } maxwait = (float)strtod(argv[narg],NULL) ; if( maxwait <= 0.0f ) maxwait = 9.0f ; narg++ ; continue ; } /** -shm [20 May 2008] **/ if( strcmp(argv[narg],"-shm") == 0 ){ strcpy( afni_host , "." ) ; narg++ ; continue ; } /** -host name **/ if( strncmp(argv[narg],"-host",5) == 0 ){ narg++ ; if( narg >= argc ){ fprintf(stderr,"** -host needs a following name!\a\n"); exit(1); } strcpy( afni_host , argv[narg] ) ; narg++ ; continue ; } /** -name sss **/ if( strncmp(argv[narg],"-name",5) == 0 ){ narg++ ; if( narg >= argc ){ fprintf(stderr,"** -name needs a following string!\a\n"); exit(1); } strcpy( afni_name , argv[narg] ) ; narg++ ; continue ; } /** -v **/ if( strncmp(argv[narg],"-v",2) == 0 ){ afni_verbose = 1 ; narg++ ; continue ; } /** -port pp **/ if( strncmp(argv[narg],"-port",4) == 0 ){ narg++ ; if( narg >= argc ){ fprintf(stderr,"** -port needs a following argument!\a\n"); exit(1); } afni_port = strtol( argv[narg] , NULL , 10 ) ; if( afni_port <= 1024 ){ fprintf(stderr,"** -port needs an argument > 1024!\a\n"); exit(1); } if( strcmp(afni_host,".") == 0 ) strcpy(afni_host,"localhost") ; narg++ ; continue ; } /*** -com 'command this' */ if( strncmp(argv[narg],"-com",4) == 0 ){ narg++ ; if( narg >= argc ){ fprintf(stderr,"** -com needs a following argument!\a\n"); exit(1); } if (argv[narg] && strlen(argv[narg]) >= PLUGOUT_COM_LENGTH) { fprintf(stderr, "** Command length must be smaller than %d characters.\n" " If you really need a longer command let us know.\n" " Your command is %d characters long.\n" , PLUGOUT_COM_LENGTH, (int)strlen(argv[narg])); exit(1); } if (N_com < 1024) { com[N_com] = argv[narg]; ++N_com; } else { fprintf( stderr, "** Only 1024 -com options allowed. Are you nuts?\a\n"); exit(1); } narg++ ; continue ; } /*** -quit */ if( strncmp(argv[narg],"-quit",5) == 0 ){ DontWait = 1 ; narg++ ; continue ; } /** Je ne sais pas **/ fprintf(stderr,"** Unrecognized option: %s\a\n",argv[narg]) ; suggest_best_prog_option(argv[0], argv[narg]); exit(1) ; } if (DontWait && !N_com) { fprintf( stderr, "** WARNING: -quit option is meaningless without -com option.\n"); DontWait = 0; } /***** Loop and check in with AFNI every 100 msec *****/ iochan_enable_perror(0) ; /* 27 Dec 2010 */ while( 1 ){ ii = afni_io() ; /* commune with AFNI */ if( ii < 0 ) exit(exit_status) ; /* bad trip? then die */ iochan_sleep(100) ; /* perchance to dream */ } }