@@ -12,24 +12,34 @@ Output a summary table for neuroimaging files (resolution, dimensionality, etc.)
12
12
"""
13
13
from __future__ import division , print_function , absolute_import
14
14
15
- __author__ = 'Yaroslav Halchenko'
16
- __copyright__ = 'Copyright (c) 2011-2015 Yaroslav Halchenko ' \
17
- 'and NiBabel contributors'
18
- __license__ = 'MIT'
19
-
20
15
import re
21
16
import sys
17
+
18
+ import numpy as np
19
+ import nibabel as nib
20
+
22
21
from math import ceil
22
+ from collections import defaultdict
23
23
from optparse import OptionParser , Option
24
24
from io import StringIO
25
+ from nibabel .py3k import asunicode
26
+ from nibabel .externals .six .moves import xrange
25
27
26
- import numpy as np
28
+ __author__ = 'Yaroslav Halchenko'
29
+ __copyright__ = 'Copyright (c) 2011-2016 Yaroslav Halchenko ' \
30
+ 'and NiBabel contributors'
31
+ __license__ = 'MIT'
27
32
28
- import nibabel as nib
29
- from nibabel .py3k import asunicode
30
33
31
34
# global verbosity switch
32
35
verbose_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
33
43
34
44
def verbose (l , msg ):
35
45
"""Print `s` if `l` is less than the `verbose_level`
@@ -40,11 +50,10 @@ def verbose(l, msg):
40
50
41
51
42
52
def error (msg , exit_code ):
43
- print >> sys .stderr , msg
53
+ print >> sys .stderr , msg
44
54
sys .exit (exit_code )
45
55
46
56
47
-
48
57
def table2string (table , out = None ):
49
58
"""Given list of lists figure out their common widths and print to out
50
59
@@ -65,18 +74,19 @@ def table2string(table, out=None):
65
74
out = StringIO ()
66
75
67
76
# 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 )
70
80
71
81
for i , table_ in enumerate (table ):
72
- table [i ] += ['' ] * (Nelements_max - len (table_ ))
82
+ table [i ] += ['' ] * (nelements_max - len (table_ ))
73
83
74
84
# figure out lengths within each column
75
85
atable = np .asarray (table )
76
86
# eat whole entry while computing width for @w (for wide)
77
87
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 ]
80
90
string = ""
81
91
for i , table_ in enumerate (table ):
82
92
string_ = ""
@@ -85,26 +95,26 @@ def table2string(table, out=None):
85
95
if item .startswith ('@' ):
86
96
align = item [1 ]
87
97
item = item [2 :]
88
- if not align in ['l' , 'r' , 'c' , 'w' ]:
98
+ if align not in ['l' , 'r' , 'c' , 'w' ]:
89
99
raise ValueError ('Unknown alignment %s. Known are l,r,c' %
90
100
align )
91
101
else :
92
102
align = 'c'
93
103
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 )
96
106
97
107
if align in ['w' , 'c' ]:
98
108
pass
99
109
elif align == 'l' :
100
- NspacesL , NspacesR = 0 , NspacesL + NspacesR
110
+ nspacesl , nspacesr = 0 , nspacesl + nspacesr
101
111
elif align == 'r' :
102
- NspacesL , NspacesR = NspacesL + NspacesR , 0
112
+ nspacesl , nspacesr = nspacesl + nspacesr , 0
103
113
else :
104
114
raise RuntimeError ('Should not get here with align=%s' % align )
105
115
106
116
string_ += "%%%ds%%s%%%ds " \
107
- % (NspacesL , NspacesR ) % ('' , item , '' )
117
+ % (nspacesl , nspacesr ) % ('' , item , '' )
108
118
string += string_ .rstrip () + '\n '
109
119
out .write (asunicode (string ))
110
120
@@ -113,15 +123,17 @@ def table2string(table, out=None):
113
123
out .close ()
114
124
return value
115
125
116
- def ap (l , format , sep = ', ' ):
126
+
127
+ def ap (l , format_ , sep = ', ' ):
117
128
"""Little helper to enforce consistency"""
118
129
if l == '-' :
119
130
return l
120
- ls = [format % x for x in l ]
131
+ ls = [format_ % x for x in l ]
121
132
return sep .join (ls )
122
133
134
+
123
135
def safe_get (obj , name ):
124
- """
136
+ """A getattr which would return '-' if getattr fails
125
137
"""
126
138
try :
127
139
f = getattr (obj , 'get_' + name )
@@ -130,11 +142,12 @@ def safe_get(obj, name):
130
142
verbose (2 , "get_%s() failed -- %s" % (name , e ))
131
143
return '-'
132
144
145
+
133
146
def get_opt_parser ():
134
147
# use module docstring for help output
135
148
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__ )
138
151
139
152
p .add_options ([
140
153
Option ("-v" , "--verbose" , action = "count" ,
@@ -149,13 +162,23 @@ def get_opt_parser():
149
162
action = "store_true" , dest = 'stats' , default = False ,
150
163
help = "Output basic data statistics" ),
151
164
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
+
152
174
Option ("-z" , "--zeros" ,
153
175
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
+ ])
156
178
157
179
return p
158
180
181
+
159
182
def proc_file (f , opts ):
160
183
verbose (1 , "Loading %s" % f )
161
184
@@ -168,21 +191,21 @@ def proc_file(f, opts):
168
191
verbose (2 , "Failed to gather information -- %s" % str (e ))
169
192
return row
170
193
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' )]
174
197
# 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 )]:
178
201
row += ['@l*%.3g+%.3g' % h .get_slope_inter ()]
179
202
else :
180
- row += [ '' ]
203
+ row += ['' ]
181
204
182
- if ( hasattr (h , 'extensions' ) and len (h .extensions ) ):
205
+ if hasattr (h , 'extensions' ) and len (h .extensions ):
183
206
row += ['@l#exts: %d' % len (h .extensions )]
184
207
else :
185
- row += [ '' ]
208
+ row += ['' ]
186
209
187
210
if opts .header_fields :
188
211
# signals "all fields"
@@ -194,16 +217,16 @@ def proc_file(f, opts):
194
217
header_fields = opts .header_fields .split (',' )
195
218
196
219
for f in header_fields :
197
- if not f : # skip empty
220
+ if not f : # skip empty
198
221
continue
199
222
try :
200
223
row += [str (h [f ])]
201
224
except (KeyError , ValueError ):
202
- row += [ 'error' ]
225
+ row += [_err () ]
203
226
204
227
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 ()):
207
230
row += ['sform' ]
208
231
else :
209
232
row += ['' ]
@@ -212,21 +235,34 @@ def proc_file(f, opts):
212
235
if isinstance (h , nib .AnalyzeHeader ):
213
236
row += ['' ]
214
237
else :
215
- row += ['error' ]
238
+ row += [_err () ]
216
239
217
- if opts .stats :
240
+ if opts .stats or opts . counts :
218
241
# We are doomed to load data
219
242
try :
220
243
d = vol .get_data ()
221
244
if not opts .stats_zeros :
222
245
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 ()]
230
266
return row
231
267
232
268
0 commit comments