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 compatiable 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 95 self.__elementsize = N.array(elementsize, ndmin=1) 96 self.__Ndims = len(self.__elementsize) 97 if compatmask is None: 98 self.__compatmask = N.ones(self.__elementsize.shape, dtype='bool') 99 else: 100 self.__compatmask = N.array(compatmask, dtype='bool') 101 if not self.__elementsize.shape == self.__compatmask.shape: 102 raise ValueError, '`compatmask` is of incompatible shape ' \ 103 '(need %s, got %s)' % (`self.__elementsize.shape`, 104 `self.__compatmask.shape`)
105 106
107 - def _expandRadius(self, radius):
108 # expand radius to be equal along all dimensions if just scalar 109 # is provided 110 if N.isscalar(radius): 111 radius = N.array([radius] * len(self.__elementsize), dtype='float') 112 else: 113 radius = N.array(radius, dtype='float') 114 115 return radius
116 117
118 - def _computeFilter(self, radius):
119 """ (Re)compute filter_coord based on given radius 120 """ 121 if not N.all(radius[self.__compatmask][0] == radius[self.__compatmask]): 122 raise ValueError, \ 123 "Currently only neighborhood spheres are supported, " \ 124 "not ellipsoids." 125 # store radius in compatible space 126 compat_radius = radius[self.__compatmask][0] 127 # compute radius in units of elementsize per axis 128 elementradius_per_axis = radius / self.__elementsize 129 130 # build prototype search space 131 filter_radiuses = N.ceil(N.abs(elementradius_per_axis)).astype('int') 132 filter_center = filter_radiuses 133 comp_center = filter_center[self.__compatmask] \ 134 * self.__elementsize[self.__compatmask] 135 filter_mask = N.ones((filter_radiuses * 2) + 1, dtype='bool') 136 137 # get coordinates of all elements 138 f_coords = N.transpose(filter_mask.nonzero()) 139 140 # but start with empty mask 141 filter_mask[:] = False 142 143 # check all filter element 144 for coord in f_coords: 145 dist = self.__distance_function( 146 coord[self.__compatmask] 147 * self.__elementsize[self.__compatmask], 148 comp_center) 149 # compare with radius 150 if dist <= compat_radius: 151 # zero too distant 152 filter_mask[N.array(coord, ndmin=2).T.tolist()] = True 153 154 155 self.__filter_coord = N.array( filter_mask.nonzero() ).T \ 156 - filter_center 157 self.__filter_radius = radius
158 159
160 - def getNeighbors(self, origin, radius=0):
161 """Returns coordinates of the neighbors which are within 162 distance from coord. 163 164 :Parameters: 165 origin: 1D array 166 The center coordinate of the neighborhood. 167 radius: scalar | sequence 168 If a scalar, the radius is treated as identical along all dimensions 169 of the dataspace. If a sequence, it defines a per dimension radius, 170 thus has to have the same number of elements as dimensions. 171 Currently, only spherical neighborhoods are supported. Therefore, 172 the radius has to be equal along all dimensions flagged as having 173 compatible dataspace metrics. It is, however, possible to define 174 variant radii for all other dimensions. 175 """ 176 if len(origin) != self.__Ndims: 177 raise ValueError("Obtained coordinates [%s] which have different " 178 "number of dimensions (%d) from known " 179 "elementsize" % (`origin`, self.__Ndims)) 180 181 # take care of postprocessing the radius the ensure validity of the next 182 # conditional 183 radius = self._expandRadius(radius) 184 if N.any(radius != self.__filter_radius): 185 self._computeFilter(radius) 186 187 # for the ease of future references, it is better to transform 188 # coordinates into tuples 189 return origin + self.__filter_coord
190 191
192 - def _setFilter(self, filter_coord):
193 """Lets allow to specify some custom filter to use 194 """ 195 self.__filter_coord = filter_coord
196 197
198 - def _getFilter(self):
199 """Lets allow to specify some custom filter to use 200 """ 201 return self.__filter_coord
202
203 - def _setElementSize(self, v):
204 # reset filter radius 205 _elementsize = N.array(v, ndmin=1) 206 # assure that it is read-only and it gets reassigned 207 # only as a whole to trigger this house-keeping 208 _elementsize.flags.writeable = False 209 self.__elementsize = _elementsize 210 self.__Ndims = len(_elementsize) 211 self.__filter_radius = None
212 213 filter_coord = property(fget=_getFilter, fset=_setFilter) 214 elementsize = property(fget=lambda self: self.__elementsize, 215 fset=_setElementSize)
216 217 # Template for future classes 218 # 219 # class MeshMetric(Metric): 220 # """Return list of neighboring points on a mesh 221 # """ 222 # def getNeighbors(self, origin, distance=0): 223 # """Return neighbors""" 224 # raise NotImplementedError 225