|
| 1 | +#!/usr/bin/python |
| 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 | +### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## |
| 10 | +""" |
| 11 | +Output a summary table for neuroimaging files (resolution, dimensionality, etc.) |
| 12 | +""" |
| 13 | + |
| 14 | +__author__ = 'Yaroslav Halchenko' |
| 15 | +__copyright__ = 'Copyright (c) 2011-2012 Yaroslav Halchenko ' \ |
| 16 | + 'and NiBabel contributors' |
| 17 | +__license__ = 'MIT' |
| 18 | + |
| 19 | +import re |
| 20 | +import sys |
| 21 | + |
| 22 | +import nibabel as nib |
| 23 | +import numpy as np |
| 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' |
| 89 | + |
| 90 | + NspacesL = max(ceil((col_width[j] - len(item))/2.0), 0) |
| 91 | + NspacesR = max(col_width[j] - NspacesL - len(item), 0) |
| 92 | + |
| 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 |
| 101 | + |
| 102 | + string_ += "%%%ds%%s%%%ds " \ |
| 103 | + % (NspacesL, NspacesR) % ('', item, '') |
| 104 | + string += string_.rstrip() + '\n' |
| 105 | + out.write(string) |
| 106 | + |
| 107 | + if print2string: |
| 108 | + value = out.getvalue() |
| 109 | + out.close() |
| 110 | + return value |
| 111 | + |
| 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) |
| 118 | + |
| 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'] |
| 183 | + else: |
| 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 += [''] |
| 189 | + else: |
| 190 | + row += ['error'] |
| 191 | + |
| 192 | + if opts.stats: |
| 193 | + # We are doomed to load data |
| 194 | + try: |
| 195 | + d = vol.get_data() |
| 196 | + if not opts.stats_zeros: |
| 197 | + d = d[np.nonzero(d)] |
| 198 | + # just # of elements |
| 199 | + row += ["[%d] " % np.prod(d.shape)] |
| 200 | + # stats |
| 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)) |
| 224 | + |
| 225 | + |
| 226 | +if __name__ == '__main__': |
| 227 | + main() |
0 commit comments