1#!/usr/local/bin/python3.8
2
3# python3 status: ready
4
5# system libraries
6import sys, os
7
8# AFNI libraries
9from afnipy import afni_util as UTIL
10from afnipy import option_list as OL
11
12# ----------------------------------------------------------------------
13# globals
14
15g_program = 'parse_fs_lt_log.py'
16
17g_help_01 = """
18=============================================================================
19parse_fs_lt_log.py      - parse FreeSurfer labeltable log file
20
21   Get labeltable indices from a rank log file, such as:
22
23        aparc+aseg_rank.niml.lt.log
24
25   usage: parse_fs_lt_log.py -logfile aparc+aseg_rank.niml.lt.log  \\
26                             -labels CC_Posterior CC_Mid_Posterior
27
28"""
29g_help_examples = """
30------------------------------------------
31examples:
32
33   Example 0: common usage - simply get original indices for aparc+aseg.nii
34
35      parse_fs_lt_log.py -logfile aparc+aseg_rank.niml.lt.log \\
36                         -labels FS_white_matter -verb 0 -show_orig
37
38   Example 1: get known FreeSurfer labels
39
40      parse_fs_lt_log.py -logfile aparc+aseg_rank.niml.lt.log  \\
41                         -labels FS_white_matter
42
43      parse_fs_lt_log.py -logfile aparc+aseg_rank.niml.lt.log  \\
44                         -labels FS_ventricles
45
46   Example 2: get a specific list of list labels
47
48      parse_fs_lt_log.py -logfile aparc+aseg_rank.niml.lt.log  \\
49                         -labels CC_Posterior CC_Mid_Posterior
50
51   Example 3: get known plus extra labels
52
53      parse_fs_lt_log.py -logfile aparc+aseg_rank.niml.lt.log             \\
54                         -labels FS_white_matter Left-Cerebellum-Exterior \\
55                         -show_all_orig
56
57"""
58g_help_02 = """
59------------------------------------------
60terminal options:
61
62   -help                : show this help
63   -hist                : show the revision history
64   -ver                 : show the version number
65
66------------------------------------------
67process options:
68
69   -labels              : specify a list of labels to search for
70
71     e.g. -labels Left-Cerebral-White-Matter  Left-Cerebellum-White-Matter  \\
72                  Right-Cerebral-White-Matter Right-Cerebellum-White-Matter \\
73                  CC_Posterior CC_Mid_Posterior CC_Central CC_Mid_Anterior  \\
74                  CC_Anterior Brain-Stem
75
76     e.g. -labels FS_white_matter
77
78     For convenience, there are 2 label groups:
79
80        FS_white_matter (as in the example):
81
82             Left-Cerebral-White-Matter  Left-Cerebellum-White-Matter
83             Right-Cerebral-White-Matter Right-Cerebellum-White-Matter
84             CC_Posterior CC_Mid_Posterior CC_Central CC_Mid_Anterior
85             CC_Anterior Brain-Stem
86
87        FS_ventricles
88
89             Left-Lateral-Ventricle Left-Inf-Lat-Vent
90             3rd-Ventricle 4th-Ventricle CSF
91             Right-Lateral-Ventricle Right-Inf-Lat-Vent 5th-Ventricle
92
93   -logfile             : specify rank log file
94
95      e.g. -logfile aparc+aseg_rank.niml.lt.log
96
97------------------------------------------
98R Reynolds    May, 2016
99=============================================================================
100"""
101
102def show_help():
103   print(g_help_01 + g_help_examples + g_help_02)
104
105g_todo = """
106   todo list:
107
108        - eat more cheese
109"""
110
111g_history = """
112   parse_fs_lt_log.py history:
113
114   0.0  May 23, 2016 - initial version
115"""
116
117g_version = "parse_fs_lt_log.py version 0.0, May 23, 2016"
118
119# default label lists
120g_fs_wm_labels = [ \
121    'Left-Cerebral-White-Matter', 'Left-Cerebellum-White-Matter',
122    'Right-Cerebral-White-Matter', 'Right-Cerebellum-White-Matter',
123    'CC_Posterior', 'CC_Mid_Posterior', 'CC_Central', 'CC_Mid_Anterior',
124    'CC_Anterior', 'Brain-Stem' ]
125
126g_fs_vent_labels = [ \
127    'Left-Lateral-Ventricle', 'Left-Inf-Lat-Vent',
128    '3rd-Ventricle', '4th-Ventricle', 'CSF',
129    'Right-Lateral-Ventricle', 'Right-Inf-Lat-Vent', '5th-Ventricle' ]
130
131# what to show
132SHOW_ORIG = 1
133SHOW_RANK = 2
134SHOW_ALL_ORIG  = 4
135
136class MyInterface:
137   """interface class for MyLibrary (whatever that is)
138   """
139   def __init__(self, verb=1):
140      # main variables
141      self.valid_opts      = None
142      self.user_opts       = None
143
144      # control
145      self.verb            = 1
146
147      # logfile name parsing
148      self.logfile         = ''
149      self.labels          = []
150      self.show_vals       = SHOW_ORIG | SHOW_RANK
151
152      # initialize valid_opts
153      self.valid_opts = self.get_valid_opts()
154
155   def get_valid_opts(self):
156      vopts = OL.OptionList('valid opts')
157
158      # short, terminal arguments
159      vopts.add_opt('-help', 0, [], helpstr='display program help')
160      vopts.add_opt('-help_examples', 0, [], helpstr='display program examples')
161      vopts.add_opt('-hist', 0, [], helpstr='display the modification history')
162      vopts.add_opt('-ver', 0, [], helpstr='display the current version number')
163
164      # general options
165      vopts.add_opt('-labels', -1, [],
166                    helpstr='specify labels to search for')
167      vopts.add_opt('-logfile', 1, [],
168                    helpstr='specify input label table log file')
169      vopts.add_opt('-show_all_orig', 0, [],
170                    helpstr='show all unranked indices, even if not found')
171      vopts.add_opt('-show_orig', 0, [],
172                    helpstr='only show unranked indices')
173      vopts.add_opt('-show_rank', 0, [],
174                    helpstr='only show ranked indices')
175      vopts.add_opt('-verb', 1, [], helpstr='set the verbose level (def=1)')
176
177      vopts.sort()
178
179      return vopts
180
181   def process_options(self):
182      """return  1 on valid and exit
183         return  0 on valid and continue
184         return -1 on invalid
185      """
186
187      argv = sys.argv
188
189      # process any optlist_ options
190      self.valid_opts.check_special_opts(argv)
191
192      # process terminal options without the option_list interface
193      # (so that errors are not reported)
194
195      # if no arguments are given, do default processing
196      if '-help' in argv or len(argv) < 2:
197         show_help()
198         return 1
199
200      if '-help_examples' in argv:
201         print(g_help_examples)
202         return 1
203
204      if '-hist' in argv:
205         print(g_history)
206         return 1
207
208      if '-show_valid_opts' in argv:
209         self.valid_opts.show('', 1)
210         return 1
211
212      if '-ver' in argv:
213         print(g_version)
214         return 1
215
216      # ============================================================
217      # read options specified by the user
218      self.user_opts = OL.read_options(argv, self.valid_opts)
219      uopts = self.user_opts            # convenience variable
220      if not uopts: return -1           # error condition
221
222      # ------------------------------------------------------------
223      # process verb first
224
225      val, err = uopts.get_type_opt(int, '-verb')
226      if val != None and not err: self.verb = val
227
228      # ------------------------------------------------------------
229      # process options sequentially, to make them like a script
230      errs = 0
231      for opt in self.user_opts.olist:
232         # check for anything to skip
233         if opt.name == '-verb': pass
234
235         elif opt.name == '-logfile':
236            self.logfile, err = uopts.get_string_opt('', opt=opt)
237            if self.logfile == None or err:
238               print('** failed to read -logfile name')
239               errs +=1
240
241         elif opt.name == '-labels':
242            self.labels, err = uopts.get_string_list('', opt=opt)
243            if self.labels == None or err:
244               print('** option -labels: failed to process option')
245               errs +=1
246
247         elif opt.name == '-show_all_orig':
248            pass        # process later
249
250         elif opt.name == '-show_orig':
251            self.show_vals = SHOW_ORIG
252
253         elif opt.name == '-show_rank':
254            self.show_vals = SHOW_RANK
255
256      if self.user_opts.find_opt('-show_all_orig'):
257         if not (self.show_vals & SHOW_ORIG):
258            print("** -show_all_orig requires showing orig")
259            errs += 1
260         self.show_vals |= SHOW_ALL_ORIG
261
262      # allow early and late error returns
263      if errs: return -1
264
265      # ------------------------------------------------------------
266      ## apply any trailing logic
267
268      if self.logfile == '':
269         print('** missing -logfile option')
270         errs += 1
271
272      if len(self.labels) < 1:
273         print('** missing -labels to search for')
274         errs += 1
275
276      if errs: return -1
277
278      return 0
279
280   def expand_labels(self):
281      """replace known label groups with actual labels
282            FS_white_matter      - white matter labels
283            FS_ventricles        - fentricle labels
284      """
285      newlabs = []
286      for label in self.labels:
287         if label == 'FS_white_matter': newlabs.extend(g_fs_wm_labels)
288         elif label == 'FS_ventricles': newlabs.extend(g_fs_vent_labels)
289         else:                          newlabs.append(label)
290
291      self.labels = newlabs
292
293   def make_label_list(self, lines):
294      """create list of [label, orig, rank] values
295
296         only allow lines that look like:   XXX  INTEGER   LABEL
297      """
298
299      if self.verb: print()
300
301      llist = []
302      for line in lines:
303         lvals = line.split()
304         if len(lvals) < 3: continue
305
306         rankstr = lvals[0]
307         origstr = lvals[1]
308         label = lvals[2]
309
310         # view in reverse order, to not waste time
311         if label not in self.labels: continue
312
313         try: vv = int(origstr)
314         except: continue
315
316         # rankstring might be an integer, else not_found and finally None
317         try: vv = int(rankstr)
318         except: rankstr = 'not_found'
319
320         if self.verb: print('%-35s  %-5s -> %s' % (label, origstr, rankstr))
321
322         if rankstr == 'not_found': rankstr = None
323         llist.append([label, origstr, rankstr])
324
325      if self.verb: print()
326
327      return llist
328
329
330   def process_logfile(self):
331      """main function to process logfile
332      """
333
334      # replace any known label groups
335      self.expand_labels()
336
337      # read logfile
338      inlines = UTIL.read_text_file(self.logfile, lines=1, verb=self.verb)
339
340      llist = self.make_label_list(inlines)
341
342      if self.show_vals & SHOW_RANK:
343         if self.verb: pref = 'rank : '
344         else:         pref = ''
345         lshow = [ll[2] for ll in llist if ll[2] != None]
346         print('%s%s' % (pref, ','.join(lshow)))
347      if self.show_vals & SHOW_ORIG:
348         if self.verb: pref = 'orig : '
349         else:         pref = ''
350         if self.show_vals & SHOW_ALL_ORIG:
351            lshow = [ll[1] for ll in llist]
352         else:
353            lshow = [ll[1] for ll in llist if ll[2] != None]
354         print('%s%s' % (pref, ','.join(lshow)))
355
356      return 0
357
358def main():
359   me = MyInterface()
360   if not me: return 1
361
362   rv = me.process_options()
363   if rv > 0: return 0  # exit with success
364   if rv < 0:           # exit with error status
365      print('** failed to process options...')
366      return 1
367
368   if me.process_logfile(): return 1
369
370   return 0
371
372if __name__ == '__main__':
373   sys.exit(main())
374
375
376