Package mvpa :: Package mappers :: Module boxcar

[hide private]
[frames] | no frames]

# Source Code for Module mvpa.mappers.boxcar

      1  # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
      2  # vi: set ft=python sts=4 ts=4 sw=4 et:
      3  ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
      4  #
      5  #   See COPYING file distributed along with the PyMVPA package for the
      6  #   copyright and license terms.
      7  #
      8  ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
      9  """Data mapper"""
     10
     11  __docformat__ = 'restructuredtext'
     12
     13  import numpy as N
     14
     15  from mvpa.base.dochelpers import enhancedDocString
     16  from mvpa.mappers.base import Mapper
     17  from mvpa.misc.support import isInVolume
     18
     19  if __debug__:
     20      from mvpa.base import debug
     21
     22 -class BoxcarMapper(Mapper):
     23      """Mapper to combine multiple samples into a single sample.
     24
     25      .. note::
     26
     27        This mapper is somewhat unconventional since it doesn't preserve number
     28        of samples (ie the size of 0-th dimension).
     29      """
     30
     31      _COLLISION_RESOLUTIONS = ['mean']
     32
     33 -    def __init__(self, startpoints, boxlength, offset=0,
     34                   collision_resolution='mean'):
     35          """
     36          :Parameters:
     37            startpoints: sequence
     38              Index values along the first axis of 'data'.
     39            boxlength: int
     40              The number of elements after 'startpoint' along the first axis of
     41              'data' to be considered for the boxcar.
     42            offset: int
     43              The offset between the provided starting point and the actual start
     44              of the boxcar.
     45            collision_resolution : 'mean'
     46              if a sample belonged to multiple output samples, then on reverse,
     47              how to resolve the value
     48          """
     49          Mapper.__init__(self)
     50
     51          startpoints = N.asanyarray(startpoints)
     52          if N.issubdtype(startpoints.dtype, 'i'):
     53              self.startpoints = startpoints
     54          else:
     55              if __debug__:
     56                  debug('MAP', "Boxcar: obtained startpoints are not of int type."
     57                        " Rounding and changing dtype")
     58              self.startpoints = N.asanyarray(N.round(startpoints), dtype='i')
     59
     60          # Sanity checks
     61          if boxlength < 1:
     62              raise ValueError, "Boxlength lower than 1 makes no sense."
     63          if boxlength - int(boxlength) != 0:
     64              raise ValueError, "boxlength must be an integer value."
     65
     66          self.boxlength = int(boxlength)
     67          self.offset = offset
     68          self.__selectors = None
     69
     70          if not collision_resolution in self._COLLISION_RESOLUTIONS:
     71              raise ValueError, "Unknown method to resolve the collision." \
     72                    " Valid are %s" % self._COLLISION_RESOLUTIONS
     73          self.__collision_resolution = collision_resolution
     74
     75
     76      __doc__ = enhancedDocString('BoxcarMapper', locals(), Mapper)
     77
     78
     79 -    def __repr__(self):
     80          s = super(BoxcarMapper, self).__repr__()
     81          return s.replace("(", "(boxlength=%d, offset=%d, startpoints=%s, "
     82                           "collision_resolution='%s'" %
     83                           (self.boxlength, self.offset, str(self.startpoints),
     84                            str(self.__collision_resolution)), 1)
     85
     86
     87 -    def forward(self, data):
     88          """Project an ND matrix into N+1D matrix
     89
     90          This method also handles the special of forward mapping a single 'raw'
     91          sample. Such a sample is extended (by concatenating clones of itself) to
     92          cover a full boxcar. This functionality is only availably after a full
     93          data array has been forward mapped once.
     94
     95          :Returns:
     96            array: (#startpoint, ...)
     97          """
     98          # in case the mapper is already charged
     99          if not self.__selectors is None:
    100              # if we have a single 'raw' sample (not a boxcar)
    101              # extend it to cover the full box -- useful if one
    102              # wants to forward map a mask in raw dataspace (e.g.
    103              # fMRI ROI or channel map) into an appropriate mask vector
    104              if data.shape == self._outshape[2:]:
    105                  return N.asarray([data] * self.boxlength)
    106
    107          self._inshape = data.shape
    108
    109          startpoints = self.startpoints
    110          offset = self.offset
    111          boxlength = self.boxlength
    112
    113          # check for illegal boxes
    114          for sp in self.startpoints:
    115              if ( sp + offset + boxlength - 1 > len(data)-1 ) \
    116                 or ( sp + offset < 0 ):
    117                  raise ValueError, \
    118                        'Illegal box: start: %i, offset: %i, length: %i' \
    119                        % (sp, offset, boxlength)
    120
    121          # build a list of list where each sublist contains the indexes of to be
    122          # averaged data elements
    123          self.__selectors = [ N.arange(i + offset, i + offset + boxlength) \
    124                               for i in startpoints ]
    125          selected = N.asarray([ data[ box ] for box in self.__selectors ])
    126          self._outshape = selected.shape
    127
    128          return selected
    129
    130
    131 -    def reverse(self, data):
    132          """Uncombine features back into original space.
    133
    134          Samples which were not touched by forward will get value 0 assigned
    135          """
    136          if data.shape == self._outshape:
    137              # reconstruct to full input space from the provided data
    138              # done below
    139              pass
    140          elif data.shape == self._outshape[1:]:
    141              # single sample was given, simple return it again.
    142              # this is done because other mappers also work with 'single'
    143              # samples
    144              return data
    145          else:
    146              raise ValueError, "BoxcarMapper operates either on single samples" \
    147                    " %s or on the full dataset in 'reverse()' which must have " \
    148                    "shape %s. Got data of shape %s" \
    149                    % (self._outshape[1:], self._outshape, data.shape)
    150
    151          # the rest of this method deals with reconstructing the full input
    152          # space from the boxcar samples
    153          assert(data.shape[0] == len(self.__selectors)) # am I right? :)
    154
    155          output = N.zeros(self._inshape, dtype=data.dtype)
    156          output_counts = N.zeros((self._inshape[0],), dtype=int)
    157
    158          for i, selector in enumerate(self.__selectors):
    159              output[selector, ...] += data[i, ...]
    160              output_counts[selector] += 1
    161
    162          # scale output
    163          if self.__collision_resolution == 'mean':
    164              # which samples how multiple sources?
    165              g1 = output_counts > 1
    166              # average them
    167              # doing complicated transposing to be able to process array with
    168              # nd > 2
    169              output_ = output[g1].T
    170              output_ /= output_counts[g1]
    171              output[g1] = output_.T
    172
    173          return output
    174
    175
    176 -    def getInSize(self):
    177          """Returns the number of original samples which were combined.
    178          """
    179
    180          return self._inshape[0]
    181
    182 -    def isValidOutId(self, outId):
    183          """Validate if OutId is valid
    184
    185          """
    186          try:
    187              return isInVolume(outId, self._outshape[1:])
    188          except:
    189              return False
    190
    191 -    def isValidInId(self, inId):
    192          """Validate if InId is valid
    193
    194          """
    195          try:
    196              return isInVolume(inId, self._inshape[1:])
    197          except:
    198              return False
    199
    200
    201 -    def getOutSize(self):
    202          """Returns the number of output samples.
    203          """
    204
    205          return N.prod(self._outshape[1:])
    206
    207
    208 -    def selectOut(self, outIds):
    209          """Just complain for now"""
    210          raise NotImplementedError, \
    211              "For feature selection use MaskMapper on output of the %s mapper" \
    212              % self.__class__.__name__
    213