1
2
3
4
5
6
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
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
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
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
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
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
100
101
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
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
118 compat_radius = radius[self.__compatmask][0]
119
120 elementradius_per_axis = radius / self.__elementsize
121
122
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
130 f_coords = N.transpose(filter_mask.nonzero())
131
132
133 filter_mask[:] = False
134
135
136 for coord in f_coords:
137 dist = self.__distance_function(
138 coord[self.__compatmask]
139 * self.__elementsize[self.__compatmask],
140 comp_center)
141
142 if dist <= compat_radius:
143
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
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
174
175 radius = self._expandRadius(radius)
176 if N.any(radius != self.__filter_radius):
177 self._computeFilter(radius)
178
179
180
181 return origin + self.__filter_coord
182
183
185 """Lets allow to specify some custom filter to use
186 """
187 self.__filter_coord = filter_coord
188
189
191 """Lets allow to specify some custom filter to use
192 """
193 return self.__filter_coord
194
196
197 _elementsize = N.array(v, ndmin=1)
198
199
200 _elementsize.flags.writeable = False
201 self.__elementsize = _elementsize
202 self.__Ndims = len(_elementsize)
203 self.__filter_radius = None
204
205
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
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
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
235
236
237
238
239
240
241
242