|
1 | 1 | #!/usr/bin/python
|
2 |
| -#emacs: -*- mode: python-mode; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- |
3 |
| -#ex: set sts=4 ts=4 sw=4 noet: |
| 2 | +# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- |
| 3 | +# vi: set ft=python sts=4 ts=4 sw=4 et: |
| 4 | +### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## |
| 5 | +# |
| 6 | +# See COPYING file distributed along with the NiBabel package for the |
| 7 | +# copyright and license terms. |
| 8 | +# |
| 9 | +### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## |
4 | 10 | """
|
5 |
| -Little script to list summary over given nifti files |
6 |
| -
|
7 |
| - COPYRIGHT: Yaroslav Halchenko 2011 |
8 |
| -
|
9 |
| - LICENSE: MIT |
10 |
| -
|
11 |
| - Permission is hereby granted, free of charge, to any person obtaining a copy |
12 |
| - of this software and associated documentation files (the "Software"), to deal |
13 |
| - in the Software without restriction, including without limitation the rights |
14 |
| - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
15 |
| - copies of the Software, and to permit persons to whom the Software is |
16 |
| - furnished to do so, subject to the following conditions: |
17 |
| -
|
18 |
| - The above copyright notice and this permission notice shall be included in |
19 |
| - all copies or substantial portions of the Software. |
20 |
| -
|
21 |
| - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
22 |
| - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
23 |
| - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
24 |
| - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
25 |
| - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
26 |
| - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
27 |
| - THE SOFTWARE. |
| 11 | +Output a summary table for neuroimaging files (resolution, dimensionality, etc.) |
28 | 12 | """
|
29 |
| -#-----------------\____________________________________/------------------ |
30 | 13 |
|
31 | 14 | __author__ = 'Yaroslav Halchenko'
|
32 |
| -__copyright__ = 'Copyright (c) 2011 Yaroslav Halchenko' |
| 15 | +__copyright__ = 'Copyright (c) 2011-2012 Yaroslav Halchenko ' \ |
| 16 | + 'and NiBabel contributors' |
33 | 17 | __license__ = 'MIT'
|
34 | 18 |
|
| 19 | +import re |
35 | 20 | import sys
|
36 |
| -from mvpa2.base.dochelpers import table2string |
37 |
| -from mvpa2.base import verbose, debug |
38 |
| -from mvpa2.misc.cmdline import parser, opts, Option |
| 21 | + |
39 | 22 | import nibabel as nib
|
40 | 23 | import numpy as np
|
41 | 24 |
|
| 25 | +from math import ceil |
| 26 | +from optparse import OptionParser, Option |
| 27 | +from StringIO import StringIO |
| 28 | + |
| 29 | +# global verbosity switch |
| 30 | +verbose_level = 0 |
| 31 | + |
| 32 | +def verbose(l, msg): |
| 33 | + """Print `s` if `l` is less than the `verbose_level` |
| 34 | + """ |
| 35 | + # TODO: consider using nibabel's logger |
| 36 | + if l <= int(verbose_level): |
| 37 | + print "%s%s" % (' ' * l, msg) |
| 38 | + |
| 39 | +def error(msg, exit_code): |
| 40 | + print >> sys.stderr, msg |
| 41 | + sys.exit(exit_code) |
| 42 | + |
| 43 | + |
| 44 | + |
| 45 | +def table2string(table, out=None): |
| 46 | + """Given list of lists figure out their common widths and print to out |
| 47 | +
|
| 48 | + Parameters |
| 49 | + ---------- |
| 50 | + table : list of lists of strings |
| 51 | + What is aimed to be printed |
| 52 | + out : None or stream |
| 53 | + Where to print. If None -- will print and return string |
| 54 | +
|
| 55 | + Returns |
| 56 | + ------- |
| 57 | + string if out was None |
| 58 | + """ |
| 59 | + |
| 60 | + print2string = out is None |
| 61 | + if print2string: |
| 62 | + out = StringIO() |
| 63 | + |
| 64 | + # equalize number of elements in each row |
| 65 | + Nelements_max = len(table) \ |
| 66 | + and max(len(x) for x in table) |
| 67 | + |
| 68 | + for i, table_ in enumerate(table): |
| 69 | + table[i] += [''] * (Nelements_max - len(table_)) |
| 70 | + |
| 71 | + # figure out lengths within each column |
| 72 | + atable = np.asarray(table) |
| 73 | + # eat whole entry while computing width for @w (for wide) |
| 74 | + markup_strip = re.compile('^@([lrc]|w.*)') |
| 75 | + col_width = [ max( [len(markup_strip.sub('', x)) |
| 76 | + for x in column] ) for column in atable.T ] |
| 77 | + string = "" |
| 78 | + for i, table_ in enumerate(table): |
| 79 | + string_ = "" |
| 80 | + for j, item in enumerate(table_): |
| 81 | + item = str(item) |
| 82 | + if item.startswith('@'): |
| 83 | + align = item[1] |
| 84 | + item = item[2:] |
| 85 | + if not align in ['l', 'r', 'c', 'w']: |
| 86 | + raise ValueError, 'Unknown alignment %s. Known are l,r,c' % align |
| 87 | + else: |
| 88 | + align = 'c' |
42 | 89 |
|
43 |
| -def ap(l, format, sep=', '): |
44 |
| - """Little helper to enforce consistency""" |
45 |
| - ls = [format % x for x in l] |
46 |
| - return sep.join(ls) |
47 |
| - |
48 |
| - |
49 |
| -parser.usage = """Usage: %s [options] [infile1] [infile2] ...""" % sys.argv[0] |
50 |
| -parser.option_groups = [opts.common] |
| 90 | + NspacesL = max(ceil((col_width[j] - len(item))/2.0), 0) |
| 91 | + NspacesR = max(col_width[j] - NspacesL - len(item), 0) |
51 | 92 |
|
52 |
| -parser.add_options([ |
53 |
| - Option("-s", "--stats", |
54 |
| - action="store_true", dest='stats', default=False, |
55 |
| - help="Output basic data statistics"), |
56 |
| - Option("-z", "--zeros", |
57 |
| - action="store_true", dest='stats_zeros', default=False, |
58 |
| - help="Include zeros into output basic data statistics (--stats)"), |
59 |
| - ]) |
| 93 | + if align in ['w', 'c']: |
| 94 | + pass |
| 95 | + elif align == 'l': |
| 96 | + NspacesL, NspacesR = 0, NspacesL + NspacesR |
| 97 | + elif align == 'r': |
| 98 | + NspacesL, NspacesR = NspacesL + NspacesR, 0 |
| 99 | + else: |
| 100 | + raise RuntimeError, 'Should not get here with align=%s' % align |
60 | 101 |
|
61 |
| -(options, files) = parser.parse_args() |
| 102 | + string_ += "%%%ds%%s%%%ds " \ |
| 103 | + % (NspacesL, NspacesR) % ('', item, '') |
| 104 | + string += string_.rstrip() + '\n' |
| 105 | + out.write(string) |
62 | 106 |
|
63 |
| -if verbose.level < 3: |
64 |
| - # suppress nibabel format-compliance warnings |
65 |
| - nib.imageglobals.logger.level = 50 |
| 107 | + if print2string: |
| 108 | + value = out.getvalue() |
| 109 | + out.close() |
| 110 | + return value |
66 | 111 |
|
67 |
| -if len(files): |
68 |
| - maxfnlen = max([len(f) for f in files]) |
| 112 | +def ap(l, format, sep=', '): |
| 113 | + """Little helper to enforce consistency""" |
| 114 | + if l == '-': |
| 115 | + return l |
| 116 | + ls = [format % x for x in l] |
| 117 | + return sep.join(ls) |
69 | 118 |
|
70 |
| - t = [] |
71 |
| - for f in files: |
72 |
| - row = ["%%-%ds" % maxfnlen % f] |
73 |
| - verbose(2, "Loading %s" % f) |
74 |
| - try: |
75 |
| - vol = nib.load(f) |
76 |
| - h = vol.get_header() |
77 |
| - except Exception, e: |
78 |
| - row += ['failed'] |
79 |
| - t.append(row) |
80 |
| - verbose(2, "Failed to gather information due to %s" % str(e)) |
81 |
| - continue |
82 |
| - row += [ str(h.get_data_dtype()), |
83 |
| - '@l[%s]' %ap(h.get_data_shape(), '%3g'), |
84 |
| - '@l%s' % ap(h.get_zooms(), '%.2f', 'x') ] |
85 |
| - # Slope |
86 |
| - if (h.has_data_slope or h.has_data_intercept) \ |
87 |
| - and not h.get_slope_inter() in [(1.0, 0.0), (None, None)]: |
88 |
| - row += ['@l*%.3g+%.3g' % h.get_slope_inter()] |
| 119 | +def safe_get(obj, name): |
| 120 | + """ |
| 121 | + """ |
| 122 | + try: |
| 123 | + f = getattr(obj, 'get_' + name) |
| 124 | + return f() |
| 125 | + except Exception, e: |
| 126 | + verbose(2, "get_%s() failed -- %s" % (name, e)) |
| 127 | + return '-' |
| 128 | + |
| 129 | +def get_opt_parser(): |
| 130 | + # use module docstring for help output |
| 131 | + p = OptionParser( |
| 132 | + usage="%s [OPTIONS] [FILE ...]\n\n" % sys.argv[0] + __doc__, |
| 133 | + version="%prog " + nib.__version__) |
| 134 | + |
| 135 | + p.add_options([ |
| 136 | + Option("-v", "--verbose", action="count", |
| 137 | + dest="verbose", default=0, |
| 138 | + help="Make more noise. Could be specified multiple times"), |
| 139 | + |
| 140 | + Option("-s", "--stats", |
| 141 | + action="store_true", dest='stats', default=False, |
| 142 | + help="Output basic data statistics"), |
| 143 | + |
| 144 | + Option("-z", "--zeros", |
| 145 | + action="store_true", dest='stats_zeros', default=False, |
| 146 | + help="Include zeros into output basic data statistics (--stats)"), |
| 147 | + ]) |
| 148 | + |
| 149 | + return p |
| 150 | + |
| 151 | +def proc_file(f, opts): |
| 152 | + verbose(1, "Loading %s" % f) |
| 153 | + |
| 154 | + row = ["@l%s" % f] |
| 155 | + try: |
| 156 | + vol = nib.load(f) |
| 157 | + h = vol.get_header() |
| 158 | + except Exception, e: |
| 159 | + row += ['failed'] |
| 160 | + verbose(2, "Failed to gather information -- %s" % str(e)) |
| 161 | + return row |
| 162 | + |
| 163 | + row += [ str(safe_get(h, 'data_dtype')), |
| 164 | + '@l[%s]' %ap(safe_get(h, 'data_shape'), '%3g'), |
| 165 | + '@l%s' % ap(safe_get(h, 'zooms'), '%.2f', 'x') ] |
| 166 | + # Slope |
| 167 | + if (hasattr(h, 'has_data_slope') |
| 168 | + and (h.has_data_slope or h.has_data_intercept)) \ |
| 169 | + and not h.get_slope_inter() in [(1.0, 0.0), (None, None)]: |
| 170 | + row += ['@l*%.3g+%.3g' % h.get_slope_inter()] |
| 171 | + else: |
| 172 | + row += [ '' ] |
| 173 | + |
| 174 | + if (hasattr(h, 'extensions') and len(h.extensions)): |
| 175 | + row += ['@l#exts: %d' % len(h.extensions)] |
| 176 | + else: |
| 177 | + row += [ '' ] |
| 178 | + |
| 179 | + try: |
| 180 | + if (hasattr(h, 'get_qform') and hasattr(h, 'get_sform') |
| 181 | + and (h.get_qform() != h.get_sform()).any()): |
| 182 | + row += ['sform'] |
89 | 183 | else:
|
90 |
| - row += [ '' ] |
91 |
| - |
92 |
| - if (hasattr(h, 'extensions') and len(h.extensions)): |
93 |
| - row += ['@l#exts: %d' % len(h.extensions)] |
| 184 | + row += [''] |
| 185 | + except Exception, e: |
| 186 | + verbose(2, "Failed to obtain qform or sform -- %s" % str(e)) |
| 187 | + if isinstance(h, nib.AnalyzeHeader): |
| 188 | + row += [''] |
94 | 189 | else:
|
95 |
| - row += [ '' ] |
| 190 | + row += ['error'] |
96 | 191 |
|
| 192 | + if opts.stats: |
| 193 | + # We are doomed to load data |
97 | 194 | try:
|
98 |
| - if (h.get_qform() != h.get_sform()).any(): |
99 |
| - row += ['sform'] |
100 |
| - else: |
101 |
| - row += [''] |
102 |
| - except Exception, e: |
103 |
| - verbose(2, "Failed to obtain qform or sform for %s due to %s" % (h, e)) |
104 |
| - if isinstance(h, nib.AnalyzeHeader): |
105 |
| - row += [''] |
106 |
| - else: |
107 |
| - row += ['error'] |
108 |
| - if options.stats: |
109 |
| - # We are doomed to load data |
110 | 195 | d = vol.get_data()
|
111 |
| - if not options.stats_zeros: |
| 196 | + if not opts.stats_zeros: |
112 | 197 | d = d[np.nonzero(d)]
|
113 | 198 | # just # of elements
|
114 | 199 | row += ["[%d] " % np.prod(d.shape)]
|
115 | 200 | # stats
|
116 | 201 | row += [len(d) and '%.2g:%.2g' % (np.min(d), np.max(d)) or '-']
|
| 202 | + except Exception, e: |
| 203 | + verbose(2, "Failed to obtain stats -- %s" % str(e)) |
| 204 | + row += ['error'] |
| 205 | + return row |
| 206 | + |
| 207 | + |
| 208 | +def main(): |
| 209 | + """Show must go on""" |
| 210 | + |
| 211 | + parser = get_opt_parser() |
| 212 | + (opts, files) = parser.parse_args() |
| 213 | + |
| 214 | + global verbose_level |
| 215 | + verbose_level = opts.verbose |
| 216 | + |
| 217 | + if verbose_level < 3: |
| 218 | + # suppress nibabel format-compliance warnings |
| 219 | + nib.imageglobals.logger.level = 50 |
| 220 | + |
| 221 | + rows = [proc_file(f, opts) for f in files] |
| 222 | + |
| 223 | + print(table2string(rows)) |
117 | 224 |
|
118 |
| - t.append(row) |
119 | 225 |
|
120 |
| - print(table2string(t)) |
| 226 | +if __name__ == '__main__': |
| 227 | + main() |
0 commit comments