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 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
108
109
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
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
126 compat_radius = radius[self.__compatmask][0]
127
128 elementradius_per_axis = radius / self.__elementsize
129
130
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
138 f_coords = N.transpose(filter_mask.nonzero())
139
140
141 filter_mask[:] = False
142
143
144 for coord in f_coords:
145 dist = self.__distance_function(
146 coord[self.__compatmask]
147 * self.__elementsize[self.__compatmask],
148 comp_center)
149
150 if dist <= compat_radius:
151
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
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
182
183 radius = self._expandRadius(radius)
184 if N.any(radius != self.__filter_radius):
185 self._computeFilter(radius)
186
187
188
189 return origin + self.__filter_coord
190
191
193 """Lets allow to specify some custom filter to use
194 """
195 self.__filter_coord = filter_coord
196
197
199 """Lets allow to specify some custom filter to use
200 """
201 return self.__filter_coord
202
204
205 _elementsize = N.array(v, ndmin=1)
206
207
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
218
219
220
221
222
223
224
225