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