Package mvpa :: Package mappers :: Module metric
[hide private]
[frames] | no frames]

Source Code for Module mvpa.mappers.metric

  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  """Classes and functions to provide sense of distances between sample points""" 
 10   
 11  __docformat__ = 'restructuredtext' 
 12   
 13  import numpy as N 
 14   
 15  from mvpa.clfs.distance import cartesianDistance 
 16   
17 -class Metric(object):
18 """Abstract class for any metric. 19 20 Subclasses abstract a metric of a dataspace with certain properties and can 21 be queried for structural information. Currently, this is limited to 22 neighborhood information, i.e. identifying the surround a some coordinate in 23 the respective dataspace. 24 25 At least one of the methods (getNeighbors, getNeighbor) has to be overriden 26 in every derived class. NOTE: derived #2 from derived class #1 has to 27 override all methods which were overrident in class #1 28 """ 29
30 - def getNeighbors(self, *args, **kwargs):
31 """Return the list of coordinates for the neighbors. 32 33 By default it simply constracts the list based on 34 the generator getNeighbor 35 """ 36 return [ x for x in self.getNeighbor(*args, **kwargs) ]
37 38
39 - def getNeighbor(self, *args, **kwargs):
40 """Generator to return coordinate of the neighbor. 41 42 Base class contains the simplest implementation, assuming that 43 getNeighbors returns iterative structure to spit out neighbors 44 1-by-1 45 """ 46 for neighbor in self.getNeighbors(*args, **kwargs): 47 yield neighbor
48 49 50
51 -class DescreteMetric(Metric):
52 """Find neighboring points in descretized space 53 54 If input space is descretized and all points fill in N-dimensional cube, 55 this finder returns list of neighboring points for a given distance. 56 57 For all `origin` coordinates this class exclusively operates on discretized 58 values, not absolute coordinates (which are e.g. in mm). 59 60 Additionally, this metric has the notion of compatible and incompatible 61 dataspace metrics, i.e. the descrete space might contain dimensions for 62 which computing an overall distance is not meaningful. This could, for 63 example, be a combined spatio-temporal space (three spatial dimension, 64 plus the temporal one). This metric allows to define a boolean mask 65 (`compatmask`) which dimensions share the same dataspace metrics and for 66 which the distance function should be evaluated. If a `compatmask` is 67 provided, all cordinates are projected into the subspace of the non-zero 68 dimensions and distances are computed within that space. 69 70 However, by using a per dimension radius argument for the getNeighbor 71 methods, it is nevertheless possible to define a neighborhood along all 72 dimension. For all non-compatible axes the respective radius is treated 73 as a one-dimensional distance along the respective axis. 74 """ 75
76 - def __init__(self, elementsize=1, distance_function=cartesianDistance, 77 compatmask=None):
78 """ 79 :Parameters: 80 elementsize: float | sequence 81 The extent of a dataspace element along all dimensions. 82 distance_function: functor 83 The distance measure used to determine distances between 84 dataspace elements. 85 compatmask: 1D bool array | None 86 A mask where all non-zero elements indicate dimensions 87 with compatible spacemetrics. If None (default) all dimensions 88 are assumed to have compatible spacemetrics. 89 """ 90 Metric.__init__(self) 91 self.__filter_radius = None 92 self.__filter_coord = None 93 self.__distance_function = distance_function 94 self.__elementsize = N.array(elementsize, ndmin=1) 95 self.__Ndims = len(self.__elementsize) 96 self.compatmask = compatmask
97 98
99 - def _expandRadius(self, radius):
100 # expand radius to be equal along all dimensions if just scalar 101 # is provided 102 if N.isscalar(radius): 103 radius = N.array([radius] * len(self.__elementsize), dtype='float') 104 else: 105 radius = N.array(radius, dtype='float') 106 107 return radius
108 109
110 - def _computeFilter(self, radius):
111 """ (Re)compute filter_coord based on given radius 112 """ 113 if not N.all(radius[self.__compatmask][0] == radius[self.__compatmask]): 114 raise ValueError, \ 115 "Currently only neighborhood spheres are supported, " \ 116 "not ellipsoids." 117 # store radius in compatible space 118 compat_radius = radius[self.__compatmask][0] 119 # compute radius in units of elementsize per axis 120 elementradius_per_axis = radius / self.__elementsize 121 122 # build prototype search space 123 filter_radiuses = N.ceil(N.abs(elementradius_per_axis)).astype('int') 124 filter_center = filter_radiuses 125 comp_center = filter_center[self.__compatmask] \ 126 * self.__elementsize[self.__compatmask] 127 filter_mask = N.ones((filter_radiuses * 2) + 1, dtype='bool') 128 129 # get coordinates of all elements 130 f_coords = N.transpose(filter_mask.nonzero()) 131 132 # but start with empty mask 133 filter_mask[:] = False 134 135 # check all filter element 136 for coord in f_coords: 137 dist = self.__distance_function( 138 coord[self.__compatmask] 139 * self.__elementsize[self.__compatmask], 140 comp_center) 141 # compare with radius 142 if dist <= compat_radius: 143 # zero too distant 144 filter_mask[N.array(coord, ndmin=2).T.tolist()] = True 145 146 147 self.__filter_coord = N.array( filter_mask.nonzero() ).T \ 148 - filter_center 149 self.__filter_radius = radius
150 151
152 - def getNeighbors(self, origin, radius=0):
153 """Returns coordinates of the neighbors which are within 154 distance from coord. 155 156 :Parameters: 157 origin: 1D array 158 The center coordinate of the neighborhood. 159 radius: scalar | sequence 160 If a scalar, the radius is treated as identical along all dimensions 161 of the dataspace. If a sequence, it defines a per dimension radius, 162 thus has to have the same number of elements as dimensions. 163 Currently, only spherical neighborhoods are supported. Therefore, 164 the radius has to be equal along all dimensions flagged as having 165 compatible dataspace metrics. It is, however, possible to define 166 variant radii for all other dimensions. 167 """ 168 if len(origin) != self.__Ndims: 169 raise ValueError("Obtained coordinates [%r] which have different " 170 "number of dimensions (%d) from known " 171 "elementsize" % (origin, self.__Ndims)) 172 173 # take care of postprocessing the radius the ensure validity of the next 174 # conditional 175 radius = self._expandRadius(radius) 176 if N.any(radius != self.__filter_radius): 177 self._computeFilter(radius) 178 179 # for the ease of future references, it is better to transform 180 # coordinates into tuples 181 return origin + self.__filter_coord
182 183
184 - def _setFilter(self, filter_coord):
185 """Lets allow to specify some custom filter to use 186 """ 187 self.__filter_coord = filter_coord
188 189
190 - def _getFilter(self):
191 """Lets allow to specify some custom filter to use 192 """ 193 return self.__filter_coord
194
195 - def _setElementSize(self, v):
196 # reset filter radius 197 _elementsize = N.array(v, ndmin=1) 198 # assure that it is read-only and it gets reassigned 199 # only as a whole to trigger this house-keeping 200 _elementsize.flags.writeable = False 201 self.__elementsize = _elementsize 202 self.__Ndims = len(_elementsize) 203 self.__filter_radius = None
204 205
206 - def _getCompatMask(self):
207 """Return compatmask 208 209 .. note:: 210 Don't modify in place since it would need to require to reset 211 __filter_radius whenever changed 212 """ 213 return self.__compatmask
214
215 - def _setCompatMask(self, compatmask):
216 """Set new value of compatmask 217 """ 218 if compatmask is None: 219 self.__compatmask = N.ones(self.__elementsize.shape, dtype='bool') 220 else: 221 self.__compatmask = N.array(compatmask, dtype='bool') 222 if not self.__elementsize.shape == self.__compatmask.shape: 223 raise ValueError, '`compatmask` is of incompatible shape ' \ 224 '(need %s, got %s)' % (`self.__elementsize.shape`, 225 `self.__compatmask.shape`) 226 self.__filter_radius = None # reset so filter gets recomputed
227 228 229 filter_coord = property(fget=_getFilter, fset=_setFilter) 230 elementsize = property(fget=lambda self: self.__elementsize, 231 fset=_setElementSize) 232 compatmask = property(_getCompatMask, _setCompatMask)
233 234 # Template for future classes 235 # 236 # class MeshMetric(Metric): 237 # """Return list of neighboring points on a mesh 238 # """ 239 # def getNeighbors(self, origin, distance=0): 240 # """Return neighbors""" 241 # raise NotImplementedError 242