@@ -12,24 +12,34 @@ Output a summary table for neuroimaging files (resolution, dimensionality, etc.)
1212"""
1313from __future__ import division , print_function , absolute_import
1414
15- __author__ = 'Yaroslav Halchenko'
16- __copyright__ = 'Copyright (c) 2011-2015 Yaroslav Halchenko ' \
17- 'and NiBabel contributors'
18- __license__ = 'MIT'
19-
2015import re
2116import sys
17+
18+ import numpy as np
19+ import nibabel as nib
20+
2221from math import ceil
22+ from collections import defaultdict
2323from optparse import OptionParser , Option
2424from io import StringIO
25+ from nibabel .py3k import asunicode
26+ from nibabel .externals .six .moves import xrange
2527
26- import numpy as np
28+ __author__ = 'Yaroslav Halchenko'
29+ __copyright__ = 'Copyright (c) 2011-2016 Yaroslav Halchenko ' \
30+ 'and NiBabel contributors'
31+ __license__ = 'MIT'
2732
28- import nibabel as nib
29- from nibabel .py3k import asunicode
3033
3134# global verbosity switch
3235verbose_level = 0
36+ MAX_UNIQUE = 1000 # maximal number of unique values to report for --counts
37+
38+ def _err (msg = None ):
39+ """To return a string to signal "error" in output table"""
40+ if msg is None :
41+ msg = 'error'
42+ return '!' + msg
3343
3444def verbose (l , msg ):
3545 """Print `s` if `l` is less than the `verbose_level`
@@ -40,11 +50,10 @@ def verbose(l, msg):
4050
4151
4252def error (msg , exit_code ):
43- print >> sys .stderr , msg
53+ print >> sys .stderr , msg
4454 sys .exit (exit_code )
4555
4656
47-
4857def table2string (table , out = None ):
4958 """Given list of lists figure out their common widths and print to out
5059
@@ -65,18 +74,19 @@ def table2string(table, out=None):
6574 out = StringIO ()
6675
6776 # equalize number of elements in each row
68- Nelements_max = len (table ) \
69- and max (len (x ) for x in table )
77+ nelements_max = \
78+ len (table ) and \
79+ max (len (x ) for x in table )
7080
7181 for i , table_ in enumerate (table ):
72- table [i ] += ['' ] * (Nelements_max - len (table_ ))
82+ table [i ] += ['' ] * (nelements_max - len (table_ ))
7383
7484 # figure out lengths within each column
7585 atable = np .asarray (table )
7686 # eat whole entry while computing width for @w (for wide)
7787 markup_strip = re .compile ('^@([lrc]|w.*)' )
78- col_width = [ max ( [len (markup_strip .sub ('' , x ))
79- for x in column ] ) for column in atable .T ]
88+ col_width = [max ([len (markup_strip .sub ('' , x ))
89+ for x in column ]) for column in atable .T ]
8090 string = ""
8191 for i , table_ in enumerate (table ):
8292 string_ = ""
@@ -85,26 +95,26 @@ def table2string(table, out=None):
8595 if item .startswith ('@' ):
8696 align = item [1 ]
8797 item = item [2 :]
88- if not align in ['l' , 'r' , 'c' , 'w' ]:
98+ if align not in ['l' , 'r' , 'c' , 'w' ]:
8999 raise ValueError ('Unknown alignment %s. Known are l,r,c' %
90100 align )
91101 else :
92102 align = 'c'
93103
94- NspacesL = max (ceil ((col_width [j ] - len (item ))/ 2.0 ), 0 )
95- NspacesR = max (col_width [j ] - NspacesL - len (item ), 0 )
104+ nspacesl = max (ceil ((col_width [j ] - len (item )) / 2.0 ), 0 )
105+ nspacesr = max (col_width [j ] - nspacesl - len (item ), 0 )
96106
97107 if align in ['w' , 'c' ]:
98108 pass
99109 elif align == 'l' :
100- NspacesL , NspacesR = 0 , NspacesL + NspacesR
110+ nspacesl , nspacesr = 0 , nspacesl + nspacesr
101111 elif align == 'r' :
102- NspacesL , NspacesR = NspacesL + NspacesR , 0
112+ nspacesl , nspacesr = nspacesl + nspacesr , 0
103113 else :
104114 raise RuntimeError ('Should not get here with align=%s' % align )
105115
106116 string_ += "%%%ds%%s%%%ds " \
107- % (NspacesL , NspacesR ) % ('' , item , '' )
117+ % (nspacesl , nspacesr ) % ('' , item , '' )
108118 string += string_ .rstrip () + '\n '
109119 out .write (asunicode (string ))
110120
@@ -113,15 +123,17 @@ def table2string(table, out=None):
113123 out .close ()
114124 return value
115125
116- def ap (l , format , sep = ', ' ):
126+
127+ def ap (l , format_ , sep = ', ' ):
117128 """Little helper to enforce consistency"""
118129 if l == '-' :
119130 return l
120- ls = [format % x for x in l ]
131+ ls = [format_ % x for x in l ]
121132 return sep .join (ls )
122133
134+
123135def safe_get (obj , name ):
124- """
136+ """A getattr which would return '-' if getattr fails
125137 """
126138 try :
127139 f = getattr (obj , 'get_' + name )
@@ -130,11 +142,12 @@ def safe_get(obj, name):
130142 verbose (2 , "get_%s() failed -- %s" % (name , e ))
131143 return '-'
132144
145+
133146def get_opt_parser ():
134147 # use module docstring for help output
135148 p = OptionParser (
136- usage = "%s [OPTIONS] [FILE ...]\n \n " % sys .argv [0 ] + __doc__ ,
137- version = "%prog " + nib .__version__ )
149+ usage = "%s [OPTIONS] [FILE ...]\n \n " % sys .argv [0 ] + __doc__ ,
150+ version = "%prog " + nib .__version__ )
138151
139152 p .add_options ([
140153 Option ("-v" , "--verbose" , action = "count" ,
@@ -149,13 +162,23 @@ def get_opt_parser():
149162 action = "store_true" , dest = 'stats' , default = False ,
150163 help = "Output basic data statistics" ),
151164
165+ Option ("-c" , "--counts" ,
166+ action = "store_true" , dest = 'counts' , default = False ,
167+ help = "Output counts - number of entries for each numeric value "
168+ "(useful for int ROI maps)" ),
169+
170+ Option ("--all-counts" ,
171+ action = "store_true" , dest = 'all_counts' , default = False ,
172+ help = "Output all counts, even if number of unique values > %d" % MAX_UNIQUE ),
173+
152174 Option ("-z" , "--zeros" ,
153175 action = "store_true" , dest = 'stats_zeros' , default = False ,
154- help = "Include zeros into output basic data statistics (--stats)" ),
155- ])
176+ help = "Include zeros into output basic data statistics (--stats, --counts )" ),
177+ ])
156178
157179 return p
158180
181+
159182def proc_file (f , opts ):
160183 verbose (1 , "Loading %s" % f )
161184
@@ -168,21 +191,21 @@ def proc_file(f, opts):
168191 verbose (2 , "Failed to gather information -- %s" % str (e ))
169192 return row
170193
171- row += [ str (safe_get (h , 'data_dtype' )),
172- '@l[%s]' % ap (safe_get (h , 'data_shape' ), '%3g' ),
173- '@l%s' % ap (safe_get (h , 'zooms' ), '%.2f' , 'x' ) ]
194+ row += [str (safe_get (h , 'data_dtype' )),
195+ '@l[%s]' % ap (safe_get (h , 'data_shape' ), '%3g' ),
196+ '@l%s' % ap (safe_get (h , 'zooms' ), '%.2f' , 'x' )]
174197 # Slope
175- if ( hasattr (h , 'has_data_slope' )
176- and (h .has_data_slope or h .has_data_intercept )) \
177- and not h .get_slope_inter () in [(1.0 , 0.0 ), (None , None )]:
198+ if hasattr (h , 'has_data_slope' ) and \
199+ (h .has_data_slope or h .has_data_intercept ) and \
200+ not h .get_slope_inter () in [(1.0 , 0.0 ), (None , None )]:
178201 row += ['@l*%.3g+%.3g' % h .get_slope_inter ()]
179202 else :
180- row += [ '' ]
203+ row += ['' ]
181204
182- if ( hasattr (h , 'extensions' ) and len (h .extensions ) ):
205+ if hasattr (h , 'extensions' ) and len (h .extensions ):
183206 row += ['@l#exts: %d' % len (h .extensions )]
184207 else :
185- row += [ '' ]
208+ row += ['' ]
186209
187210 if opts .header_fields :
188211 # signals "all fields"
@@ -194,16 +217,16 @@ def proc_file(f, opts):
194217 header_fields = opts .header_fields .split (',' )
195218
196219 for f in header_fields :
197- if not f : # skip empty
220+ if not f : # skip empty
198221 continue
199222 try :
200223 row += [str (h [f ])]
201224 except (KeyError , ValueError ):
202- row += [ 'error' ]
225+ row += [_err () ]
203226
204227 try :
205- if (hasattr (h , 'get_qform' ) and hasattr (h , 'get_sform' )
206- and (h .get_qform () != h .get_sform ()).any ()):
228+ if (hasattr (h , 'get_qform' ) and hasattr (h , 'get_sform' ) and
229+ (h .get_qform () != h .get_sform ()).any ()):
207230 row += ['sform' ]
208231 else :
209232 row += ['' ]
@@ -212,21 +235,34 @@ def proc_file(f, opts):
212235 if isinstance (h , nib .AnalyzeHeader ):
213236 row += ['' ]
214237 else :
215- row += ['error' ]
238+ row += [_err () ]
216239
217- if opts .stats :
240+ if opts .stats or opts . counts :
218241 # We are doomed to load data
219242 try :
220243 d = vol .get_data ()
221244 if not opts .stats_zeros :
222245 d = d [np .nonzero (d )]
223- # just # of elements
224- row += ["[%d] " % np .prod (d .shape )]
225- # stats
226- row += [len (d ) and '%.2g:%.2g' % (np .min (d ), np .max (d )) or '-' ]
227- except Exception as e :
228- verbose (2 , "Failed to obtain stats -- %s" % str (e ))
229- row += ['error' ]
246+ else :
247+ # at least flatten it -- functionality below doesn't
248+ # depend on the original shape, so let's use a flat view
249+ d = d .reshape (- 1 )
250+ if opts .stats :
251+ # just # of elements
252+ row += ["@l[%d]" % np .prod (d .shape )]
253+ # stats
254+ row += [len (d ) and '@l[%.2g, %.2g]' % (np .min (d ), np .max (d )) or '-' ]
255+ if opts .counts :
256+ items , inv = np .unique (d , return_inverse = True )
257+ if len (items ) > 1000 and not opts .all_counts :
258+ counts = _err ("%d uniques. Use --all-counts" % len (items ))
259+ else :
260+ freq = np .bincount (inv )
261+ counts = " " .join ("%g:%d" % (i , f ) for i , f in zip (items , freq ))
262+ row += ["@l" + counts ]
263+ except IOError as e :
264+ verbose (2 , "Failed to obtain stats/counts -- %s" % str (e ))
265+ row += [_err ()]
230266 return row
231267
232268
0 commit comments