Source code for fsl.wrappers.fslstats

#!/usr/bin/env python
#
# fslstats.py - Wrapper for fslstats
#
# Author: Paul McCarthy <pauldmccarthy@gmail.com>
#
"""This module provides the :class:`fslstats` class, which acts as a wrapper
for the ``fslstats`` command-line tool.


.. warning:: This wrapper function will only work with FSL 6.0.2 or newer.
"""


import              io
import functools as ft
import numpy     as np

import fsl.data.image      as fslimage
from . import wrapperutils as wutils


[docs] class fslstats: """The ``fslstats`` class is a wrapper around the ``fslstats`` command-line tool. It provides an object-oriented interface - options are specified by chaining method calls and attribute accesses together. .. warning:: This wrapper function will only work with FSL 6.0.2 or newer, due to bugs in ``fslstats`` output formatting that are present in older versions. This ``fslstats`` command:: fslstats image -r -p 95 -R is equivalent to this function call:: fslstats('image').r.p(95).R.run() Any ``fslstats`` command-line option which does not require any arguments (e.g. ``-r``) can be set by accessing an attribute on a ``fslstats`` object, e.g.:: fslstats('image.nii.gz').r.run() ``fslstats`` command-line options which do require additional arguments (e.g. ``-k``) can be set by calling a method on an ``fslstats`` object, e.g.:: stats = fslstats('image.nii.gz').k('mask.nii.gz').run() The ``fslstats`` command can be executed via the :meth:`run` method. Normally, the results will be returned as a scalar floating point number, or a ``numpy`` array. Pre-options will affect the structure of the return value - see :meth:`__init__` for details. Attribute and method calls can be chained together, so a complete ``fslstats`` call can be performed in a single line, e.g.:: imgmin, imgmax = fslstats('image.nii.gz').k('mask.nii.gz').r.run() """ OPTIONS = { 'robust_minmax' : 'r', 'minmax' : 'R', 'mean_entropy' : 'e', 'mean_entropy_nz' : 'E', 'volume' : 'v', 'volume_nz' : 'V', 'mean' : 'm', 'mean_nz' : 'M', 'stddev' : 's', 'stddev_nz' : 'S', 'smallest_roi' : 'w', 'max_vox' : 'x', 'min_vox' : 'X', 'cog_mm' : 'c', 'cog_vox' : 'C', 'abs' : 'a', 'zero_naninf' : 'n', } """This dict contains options which do not require any additional arguments. They are set via attribute access on the ``fslstats`` object. """ ARG_OPTIONS = { 'lower_threshold' : 'l', 'upper_threshold' : 'u', 'percentile' : 'p', 'percentile_nz' : 'P', 'mask' : 'k', 'diff' : 'd', 'hist' : 'h', 'hist_bounded' : 'H', } """This dict contains options which require additional arguments. They are set via method calls on the ``fslstats`` object (with the additional arguments passed into the method call). """ # add {shortopt : shortopt} mappings # for all options to simplify code in # the fslstats class OPTIONS .update({v : v for v in OPTIONS .values()}) ARG_OPTIONS.update({v : v for v in ARG_OPTIONS.values()})
[docs] def __init__(self, input, t=False, K=None, sep_volumes=False, index_mask=None): """Create a ``fslstats`` object. If one of the ``t`` or ``K`` pre-options is set, e.g.:: fslstats('image_4d.nii.gz', t=True) or:: fslstats('image_4d.nii.gz', K='mask.nii.gz') then :meth:`run` will return a 2D ``numpy`` array of shape ``(nvols, nvals)`` if ``t`` is set, or ``(nlabels, nvals)`` if ``K`` is set. If both of the ``t`` and ``K`` pre-options are set, e.g.:: fslstats('image_4d.nii.gz', t=True, K='mask.nii.gz') then the result will be a 3D numpy array of shape ``(nvols, nlabels, nvals)``. If neither ``t`` or ``K`` are set, then the result will be a scalar, or a 1D ``numpy`` array. :arg input: Input image - either a file name, or an :class:`.Image` object, or a ``nibabel.Nifti1Image`` object. :arg t: Produce separate results for each 3D volume in the input image. :arg K: Produce separate results for each sub-mask within the provided mask image. :arg sep_volumes: Alias for ``t``. :arg index_mask: Alias for ``K``. """ if t is None: t = sep_volumes if K is None: K = index_mask self.__input = input self.__options = [] # pre-options must be supplied # before input image if t: self.__options.append( '-t') if K is not None: self.__options.extend(('-K', K)) self.__options.append(input)
[docs] def __getattr__(self, name): """Intercepts attribute accesses and accumulates ``fslstats`` command-line flags accordingly. """ # options which take no args # are called as attributes if name in fslstats.OPTIONS: flag = fslstats.OPTIONS[name] args = False # options which take args # are called as methods elif name in fslstats.ARG_OPTIONS: flag = fslstats.ARG_OPTIONS[name] args = True else: raise AttributeError(name) addFlag = ft.partial(self.__addFlag, flag) if args: return addFlag else: return addFlag()
def __addFlag(self, flag, *args): """Used by :meth:`__getattr__`. Add the given flag and any arguments to the accumulated list of command-line options. """ self.__options.extend(('-' + flag,) + args) return self
[docs] def run(self, raw=False, **kwargs): """Run the ``fslstats`` command-line tool. See :meth:`__init__` for a description of the return value. :arg raw: Defaults to ``False``. If ``True``, the raw standard output and error is returned, instead of a scalar/numpy array. :returns: Result of ``fslstats`` as a scalar or ``numpy`` array. All other arguments are ultimately passed through to the :func:`fsl.utils.run.run` function. """ # The parsing logic below will not work # with versions of fslstats prior to fsl # 6.0.2, due to a quirk in the output # formatting of older versions. # The default behaviour of run/runfsl # is to tee the command output streams # to the calling process streams. We # can disable this via the log argument # (but this can be overridden by the # caller). # # We don't do this if being called # whilst a wrapperconfig context is # active, as we would otherwise # potentially overwrite the log value # passed to the wrapperconfig. if not wutils.wrapperconfig.active: kwargs['log'] = kwargs.pop('log', {'tee' : False}) result = self.__run('fslstats', *self.__options, **kwargs) if raw: return result.stdout # If an index mask was used (-K), any # missing labels will result in a line # "missing label: <x>". Replace these # lines with nan before passing the # output to numpy for the conversion. result = result.stdout[0] result = result.split('\n') for i, line in enumerate(result): if 'missing label' in line: result[i] = 'nan' else: result[i] = line result = '\n'.join(result).strip() result = np.genfromtxt(io.StringIO(result)) sepvols = '-t' in self.__options lblmask = '-K' in self.__options # One line of output for each volume and # for each label (with volume the slowest # changing). Reshape to 3D. if sepvols and lblmask: # We need know the number of volumes # (or the number of labels) in order # to know how to shape the results. img = fslimage.Image(self.__input) if img.ndim >= 4: nvols = img.shape[3] else: nvols = 1 # reshape the result into # (nvals, nvols, nlbls) nlbls = int(len(result) / nvols) result = result.reshape((nvols, nlbls, -1)).squeeze() # Scalar - use numpy indexing weirdness # to get our single value out. elif result.size == 1: result = result[()] return result
@wutils.fileOrImage() @wutils.fslwrapper def __run(self, *cmd): """Run the given ``fslstats`` command. """ return [str(c) for c in cmd]