1#!/usr/local/bin/python3.8 2 3# python3 status: compatible 4 5import sys, os 6from afnipy import option_list, afni_util as UTIL, afni_base as BASE 7 8g_help_string = """ 9=========================================================================== 10gen_epi_review.py: 11 12 This program will generate an AFNI processing script that can be used 13 to review EPI data (possibly called @review_epi_data). 14 15 The @review_epi_data script is meant to provide an easy way to quickly 16 review the (preferably un-altered) EPI data. It runs afni and then a 17 looping set of drive_afni commands. 18 19 Note that there should not be another instance of 'afni' running on 20 the system when the script is run, as 'drive_afni' will communicate 21 with only the first invoked 'afni' program. 22 23 The most simple usage comes with the -dsets option, along with the 24 necessary pieces of the gen_epi_review.py command. 25 26-------------------------------------------------- 27examples: 28 29 These examples assume the EPI dataset names produced as a result of 30 the afni_proc.py processing script proc.sb23.blk, produced by the 31 command in AFNI_data4/s1.afni_proc.block, provided with the class data. 32 33 Yes, that means running the s1.afni_proc.block (tcsh) script to call 34 the afni_proc.py (python) script to produce the proc.sb23.blk (tcsh) 35 script, which calls the gen_epi_review.py (python) script to produce 36 the @review_epi_data (tcsh) script, which can be run to review your EPI 37 data. Ahhhhhhh... :) 38 39 Note that when using wildcards, the datasets must exist in the current 40 directory. But when using the {1,2,..} format, the files do not yet 41 need to exist. So command #2 could be run anywhere and still create the 42 same script, no data needed. 43 44 1. simple usage, just providing datasets (and general options) 45 46 gen_epi_review.py -dsets pb00.sb23.blk.r??.tcat+orig.HEAD 47 48 2. expand 5 runs with shell notation, rather than wildcards, and 49 specify an alternate script name 50 51 gen_epi_review.py -dsets pb00.sb23.blk.r{1,2,3,4,5}.tcat \\ 52 -script @review_epi_5runs 53 54 3. choose to see all three image windows 55 56 gen_epi_review.py -dsets pb00.sb23.blk.r*.tcat+orig.HEAD \\ 57 -windows sagittal axial coronal \\ 58 -script @review_epi_windows 59 60 4. specify the graph size and position (can do the same for image windows) 61 62 gen_epi_review.py -dsets pb00.sb23.blk.r*.tcat+orig.HEAD \\ 63 -gr_size 600 450 -gr_xoff 100 -gr_yoff 200 \\ 64 -script @review_epi_posn 65 66---------------------------------------------------------------------- 67OPTIONS: 68---------------------------------------------------------------------- 69informational arguments: 70 71 -help : display this help 72 -hist : display the modification history 73 -show_valid_opts : display all valid options (short format) 74 -ver : display the version number 75 76---------------------------------------- 77required argument: 78 79 -dsets dset1 dset2 ... : specify input datasets for processing 80 81 e.g. -dsets epi_r*+orig.HEAD 82 83 This option is used to provide a list of datasets to be processed 84 in the resulting script. 85 86---------------------------------------- 87optional arguments: 88 89 -script SCRIPT_NAME : specify the name of the generated script 90 91 e.g. -script review.epi.subj23 92 93 By default, the script name will be '@' followed by the name used 94 for the '-generate' option. So when using '-generate review_epi_data', 95 the default script name will be '@review_epi_data'. 96 97 This '-script' option can be used to override the default. 98 99 -verb LEVEL : specify a verbosity level 100 101 e.g. -verb 3 102 103 Use this option to print extra information to the screen 104 105 -windows WIN1 WIN2 ... : specify the image windows to open 106 107 e.g. -windows sagittal axial 108 109 By default, the script will open 2 image windows (sagittal and axial). 110 This option can be used to specify exactly which windows get opened, 111 and in which order. 112 113 Acceptable window names are: sagittal, axial, coronal 114 115---------------------------------------- 116geometry arguments (optional): 117 118 -im_size dimX dimY : set image dimensions, in pixels 119 120 e.g. -im_size 300 300 121 122 Use this option to alter the size of the image windows. This 123 option takes 2 parameters, the pixels in the X and Y directions. 124 125 -im_xoff XOFFSET : set the X-offset for the image, in pixels 126 127 e.g. -im_xoff 420 128 129 Use this option to alter the placement of images along the x-axis. 130 Note that the x-axis is across the screen, from left to right. 131 132 -im_yoff YOFFSET : set the Y-offset for the image, in pixels 133 134 e.g. -im_xoff 400 135 136 Use this option to alter the placement of images along the y-axis. 137 Note that the y-axis is down the screen, from top to bottom. 138 139 -gr_size dimX dimY : set graph dimensions, in pixels 140 141 e.g. -gr_size 400 300 142 143 Use this option to alter the size of the graph window. This option 144 takes 2 parameters, the pixels in the X and Y directions. 145 146 -gr_xoff XOFFSET : set the X-offset for the graph, in pixels 147 148 e.g. -gr_xoff 0 149 150 Use this option to alter the placement of the graph along the x-axis. 151 Note that the x-axis is across the screen, from left to right. 152 153 -gr_yoff YOFFSET : set the Y-offset for the graph, in pixels 154 155 e.g. -gr_xoff 400 156 157 Use this option to alter the placement of the graph along the y-axis. 158 Note that the y-axis is down the screen, from top to bottom. 159 160 161- R Reynolds June 27, 2008 162=========================================================================== 163""" 164 165g_history = """ 166 gen_epi_review.py history: 167 168 0.1 Jun 27, 2008: initial version 169 0.2 Jun 30, 2008: 170 - make script executable, decrease sleep time and add usage comment 171 0.3 Sep 23, 2008: in script, check for existence of given datasets 172 0.4 Apr 24, 2018: updated for python3 173""" 174 175g_version = "version 0.4, Apr 24, 2018" 176 177gDEF_VERB = 1 # default verbose level 178gDEF_IM_SIZE = [300,300] # image size, in pixels 179gDEF_IM_XOFF = 420 # image offset in the x direction 180gDEF_IM_YOFF = 400 # (initial) image offset in y direction 181gDEF_GR_SIZE = [400,300] # graph size, in pixels 182gDEF_GR_XOFF = 0 # graph offset in the x direction 183gDEF_GR_YOFF = 400 # (initial) graph offset in y direction 184 185gDEF_WINDOWS = ['sagittal', 'axial'] # default windows to open 186 187class GenEPIReview: 188 def __init__(self, label): 189 # general parameters 190 self.label = label 191 self.verb = gDEF_VERB 192 self.valid_opts = None # OptionList - valid options 193 self.user_opts = None # OptionList - user supplied 194 195 # required user parameters 196 self.dsets = [] # list of input datasets 197 self.adsets = [] # list of afni_name datasets 198 199 # optional parameters 200 self.script = None # script name 201 self.windows = gDEF_WINDOWS # list of windows to open 202 203 # coordinate parameters 204 self.im_size = None # image width, in pixels 205 self.im_xoff = None # x-offset of image windows 206 self.im_yoff = None # initial y-offset if images 207 self.gr_size = None # graph width, in pixels 208 self.gr_xoff = None # x-offset of image windows 209 self.gr_yoff = None # initial y-offset if images 210 211 def init_opts(self): 212 global g_help_string 213 self.valid_opts = option_list.OptionList('for input') 214 215 # short, terminal arguments 216 self.valid_opts.add_opt('-help', 0, [], \ 217 helpstr='display program help') 218 self.valid_opts.add_opt('-hist', 0, [], \ 219 helpstr='display the modification history') 220 self.valid_opts.add_opt('-show_valid_opts', 0, [], \ 221 helpstr='display all valid options') 222 self.valid_opts.add_opt('-ver', 0, [], \ 223 helpstr='display the current version number') 224 225 # required arguments 226 self.valid_opts.add_opt('-dsets', -1, [], req=1, 227 helpstr='list of input datasets') 228 229 # optional arguments 230 self.valid_opts.add_opt('-script', 1, [], 231 helpstr='name for output script') 232 self.valid_opts.add_opt('-verb', 1, [], 233 helpstr='verbose level (0=quiet, 1=default, ...)') 234 self.valid_opts.add_opt('-windows', -1, [], 235 acplist=['axial','coronal','sagittal'], 236 helpstr='choose afni image windows to display') 237 238 # corrdinate arguments 239 self.valid_opts.add_opt('-im_size', 2, [], 240 helpstr='image size, in pixels (2 integers)') 241 self.valid_opts.add_opt('-im_xoff', 1, [], 242 helpstr='x-offset for image, in pixels') 243 self.valid_opts.add_opt('-im_yoff', 1, [], 244 helpstr='y-offset for image, in pixels') 245 246 self.valid_opts.add_opt('-gr_size', 2, [], 247 helpstr='graph size, in pixels (2 integers)') 248 self.valid_opts.add_opt('-gr_xoff', 1, [], 249 helpstr='x-offset for graph, in pixels') 250 self.valid_opts.add_opt('-gr_yoff', 1, [], 251 helpstr='y-offset for graph, in pixels') 252 253 254 def read_opts(self): 255 """check for terminal arguments, then read the user options""" 256 257 # ------------------------------------------------------------ 258 # process any optlist_ options 259 self.valid_opts.check_special_opts(sys.argv) 260 261 # ------------------------------------------------------------ 262 # check for terminal args in argv (to ignore required args) 263 264 # if argv has only the program name, or user requests help, show it 265 if len(sys.argv) <= 1 or '-help' in sys.argv: 266 print(g_help_string) 267 return 0 268 269 if '-hist' in sys.argv: 270 print(g_history) 271 return 0 272 273 if '-show_valid_opts' in sys.argv: # show all valid options 274 self.valid_opts.show('', 1) 275 return 0 276 277 if '-ver' in sys.argv: 278 print(g_version) 279 return 0 280 281 # ------------------------------------------------------------ 282 # now read user options 283 284 self.user_opts = option_list.read_options(sys.argv, self.valid_opts) 285 if not self.user_opts: return 1 # error condition 286 287 return None # normal completion 288 289 def process_opts(self): 290 """apply each option""" 291 292 # ---------------------------------------- 293 # set verb first 294 self.verb,err = self.user_opts.get_type_opt(int, '-verb') 295 if err: return 1 296 if self.verb == None: self.verb = gDEF_VERB 297 298 # ---------------------------------------- 299 # required args 300 301 opt = self.user_opts.find_opt('-dsets') 302 if opt and opt.parlist: 303 self.dsets = opt.parlist 304 self.adsets = [BASE.afni_name(s) for s in opt.parlist] 305 if len(self.dsets) < 1: 306 print('** missing input dataets') 307 return 1 308 309 # ---------------------------------------- 310 # optional arguments 311 312 self.script, err = self.user_opts.get_string_opt('-script') 313 if err: return 1 314 if self.script == None: self.script = '@review_epi_data' 315 if os.path.isfile(self.script): 316 print("** script file '%s' already exists, failing..." \ 317 % self.script) 318 return 1 319 320 opt = self.user_opts.find_opt('-windows') 321 if opt and opt.parlist: 322 self.windows = opt.parlist 323 if len(self.windows) < 1: 324 print('** missing window list') 325 return 1 326 327 # ---------------------------------------- 328 # image coordinates 329 self.im_size, err = self.user_opts.get_type_list(int, '-im_size', 330 2, 'two', verb=self.verb) 331 if err: return 1 332 if not self.im_size: self.im_size = gDEF_IM_SIZE 333 334 self.im_xoff, err = self.user_opts.get_type_opt(int, '-im_xoff') 335 if err: return 1 336 if not self.im_xoff: self.im_xoff = gDEF_IM_XOFF 337 338 self.im_yoff, err = self.user_opts.get_type_opt(int, '-im_yoff') 339 if err: return 1 340 if not self.im_yoff: self.im_yoff = gDEF_IM_YOFF 341 342 343 # ---------------------------------------- 344 # graph coordinates 345 self.gr_size, err = self.user_opts.get_type_list(int, '-gr_size', 346 2, 'two', verb=self.verb) 347 if err: return 1 348 if not self.gr_size: self.gr_size = gDEF_GR_SIZE 349 350 self.gr_xoff, err = self.user_opts.get_type_opt(int, '-gr_xoff') 351 if err: return 1 352 if not self.gr_xoff: self.gr_xoff = gDEF_GR_XOFF 353 354 self.gr_yoff, err = self.user_opts.get_type_opt(int, '-gr_yoff') 355 if err: return 1 356 if not self.gr_yoff: self.gr_yoff = gDEF_GR_YOFF 357 358 # ---------------------------------------- 359 # check over the inputs 360 361 def make_script(self): 362 """create the review EPI (plugout_drive) script""" 363 364 c2 = "#!/bin/tcsh\n\n" 365 366 c2 += "# ------------------------------------------------------\n" \ 367 "# review EPI data via 'afni' and 'plugout_drive'\n\n" \ 368 "# note that when running this script, prompts to change\n" \ 369 "# datasets will appear in the terminal window\n\n" 370 371 c2 += "# ------------------------------------------------------\n" \ 372 "# set the list of datasets\n" \ 373 "set dsets = ( %s )\n\n" % \ 374 ' '.join([dset.prefix for dset in self.adsets]) 375 376 c2 += '# ------------------------------------------------------\n' \ 377 '# verify that the input data exists\n' \ 378 'if ( ! -f $dsets[1]+orig.HEAD ) then\n' \ 379 ' echo "** missing data to review (e.g. $dsets[1])"\n' \ 380 ' exit\n' \ 381 'endif\n\n' 382 383 c2 += '# ------------------------------------------------------\n' \ 384 '# start afni is listening mode, and take a brief nap\n\n' \ 385 'afni -yesplugouts &\n\n' \ 386 'sleep 5\n\n' 387 388 cmd = UTIL.add_line_wrappers(c2) 389 390 c2 = '# ------------------------------------------------------\n' \ 391 '# tell afni to load the first dataset and open windows\n\n' \ 392 'plugout_drive \\\n' \ 393 ' -com "SWITCH_UNDERLAY %s" \\\n' \ 394 % self.adsets[0].prefix 395 396 # open windows in the list 397 if self.verb>1: print('++ opening windows: %s' % ', '.join(self.windows)) 398 399 for ind in range(len(self.windows)): 400 c2 += ' -com "OPEN_WINDOW %simage \\\n' \ 401 ' geom=%dx%d+%d+%d" \\\n' % \ 402 (self.windows[ind], self.im_size[0], self.im_size[1], 403 self.im_xoff+ind*self.im_size[0], self.im_yoff) 404 405 c2 += ' -com "OPEN_WINDOW sagittalgraph \\\n' \ 406 ' geom=%dx%d+%d+%d" \\\n' % \ 407 (self.gr_size[0], self.gr_size[1], self.gr_xoff, self.gr_yoff) 408 409 # terminate the initial plugout_drive command 410 c2 += ' -quit\n\n' 411 412 c2 = UTIL.add_line_wrappers(c2) # do it early, for verb 413 414 if self.verb > 2: print('initial drive command:\n%s' % c2) 415 416 cmd += c2 417 418 cmd += 'sleep 2 # give afni time to open the windows\n\n\n' 419 420 c2 = '# ------------------------------------------------------\n' \ 421 '# process each dataset using video mode\n\n' \ 422 'foreach dset ( $dsets )\n' \ 423 ' plugout_drive \\\n' \ 424 ' -com "SWITCH_UNDERLAY $dset" \\\n' \ 425 ' -com "OPEN_WINDOW sagittalgraph \\\n' \ 426 ' keypress=a\\\n' \ 427 ' keypress=v"\\\n' \ 428 ' -quit\n\n' \ 429 ' sleep 2 # wait for plugout_drive output\n\n' \ 430 ' echo ""\n' \ 431 ' echo "++ now viewing $dset, hit enter to continue"\n' \ 432 ' set ret = $< # wait for user to hit enter\n' \ 433 'end\n\n\n' 434 435 cmd += UTIL.add_line_wrappers(c2) 436 437 c2 = '# ------------------------------------------------------\n' \ 438 '# stop video mode when the user is done\n\n' \ 439 'plugout_drive -com "OPEN_WINDOW sagittalgraph keypress=s"' \ 440 ' -quit\n\n\n' \ 441 'sleep 2 # wait for plugout_drive output\n\n' \ 442 'echo ""\n' \ 443 'echo "data review complete"\n\n\n' 444 445 cmd += UTIL.add_line_wrappers(c2) 446 447 c2 = '# ----------------------------------------------------------'\ 448 "------\n# auto-generated by gen_epi_review.py, %s\n" % g_version 449 c2 += "#\n# %s %s\n" % (os.path.basename(sys.argv[0]), 450 ' '.join(UTIL.quotize_list(sys.argv[1:],''))) 451 452 cmd += UTIL.add_line_wrappers(c2) 453 454 fp = open(self.script, "w") 455 if not fp: 456 print('** failed to open %s for writing decon script' % self.script) 457 return 1 458 459 fp.write(cmd) 460 fp.close() 461 try: 462 os.chmod(self.script, 0o755) 463 except OSError as e: 464 print(e) 465 466 return 467 468def process(): 469 review = GenEPIReview('EPI review script') 470 review.init_opts() 471 rv = review.read_opts() 472 if rv != None: # 0 is okay, else error code 473 if rv: UTIL.show_args_as_command(sys.argv,"** failed command (RO):") 474 return rv 475 476 rv = review.process_opts() 477 if rv != None: 478 if rv: UTIL.show_args_as_command(sys.argv,"** failed command (PO):") 479 return rv 480 481 rv = review.make_script() 482 if rv != None: 483 if rv: UTIL.show_args_as_command(sys.argv,"** failed command (MS):") 484 return rv 485 486 return 0 487 488if __name__ == "__main__": 489 rv = process() 490 sys.exit(rv) 491 492