1
2
3
4
5
6
7
8
9 """Data mapper which applies mask to the data"""
10
11 __docformat__ = 'restructuredtext'
12
13 import numpy as N
14
15 from mvpa.mappers.base import Mapper
16 from mvpa.base.dochelpers import enhancedDocString
17 from mvpa.misc.support import isInVolume
18
19 if __debug__:
20 from mvpa.base import debug, warning
21 from mvpa.misc.support import isSorted
22
23
25 """Mapper which uses a binary mask to select "Features" """
26
28 """Initialize MaskMapper
29
30 :Parameters:
31 mask : array
32 an array in the original dataspace and its nonzero elements are
33 used to define the features included in the dataset
34 """
35 Mapper.__init__(self, **kwargs)
36
37 self.__mask = self.__maskdim = self.__masksize = \
38 self.__masknonzerosize = self.__forwardmap = \
39 self.__masknonzero = None
40 self._initMask(mask)
41
42
43 __doc__ = enhancedDocString('MaskMapper', locals(), Mapper)
44
45
47 return "MaskMapper: %d -> %d" \
48 % (self.__masksize, self.__masknonzerosize)
49
51 s = super(MaskMapper, self).__repr__()
52 return s.replace("(", "(mask=%s," % self.__mask, 1)
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
76 """Initialize internal state with mask-derived information
77
78 It is needed to initialize structures for the fast
79 and reverse lookup to don't impose performance hit on any
80 future operation
81 """
82
83
84
85 self.__mask = (mask != 0)
86 self.__maskdim = len(mask.shape)
87 self.__masksize = N.prod(mask.shape)
88
89
90
91
92 self.__masknonzero = mask.nonzero()
93 self.__masknonzerosize = len(self.__masknonzero[0])
94
95
96
97
98
99
100
101
102
103
104 self.__forwardmap = N.zeros(mask.shape, dtype=N.int64)
105
106
107
108 self.__forwardmap[self.__masknonzero] = \
109 N.arange(self.__masknonzerosize)
110
111
113 """Map data from the original dataspace into featurespace.
114 """
115 data = N.asanyarray(data)
116 datadim = len(data.shape)
117 datashape = data.shape[(-1)*self.__maskdim:]
118 if not datashape == self.__mask.shape:
119 raise ValueError, \
120 "The shape of data to be mapped %s " % `datashape` \
121 + " does not match the mapper's mask shape %s" \
122 % `self.__mask.shape`
123
124 if self.__maskdim == datadim:
125
126
127
128 return data[ self.__mask ]
129 elif self.__maskdim+1 == datadim:
130
131
132
133 return data[ :, self.__mask ]
134 else:
135 raise ValueError, \
136 "Shape of the to be mapped data, does not match the " \
137 "mapper mask. Only one (optional) additional dimension " \
138 "exceeding the mask shape is supported."
139
140
142 """Reverse map data from featurespace into the original dataspace.
143 """
144 data = N.asanyarray(data)
145 datadim = len(data.shape)
146 if not datadim in [1, 2]:
147 raise ValueError, \
148 "Only 2d or 1d data can be reverse mapped. "\
149 "Got data of shape %s" % (data.shape,)
150
151 if datadim == 1:
152
153
154
155 if __debug__ and self.nfeatures != len(data):
156 raise ValueError, \
157 "Cannot reverse map data with %d elements, whenever " \
158 "mask knows only %d" % (len(data), self.nfeatures)
159 mapped = N.zeros(self.__mask.shape, dtype=data.dtype)
160 mapped[self.__mask] = data
161 elif datadim == 2:
162 mapped = N.zeros(data.shape[:1] + self.__mask.shape,
163 dtype=data.dtype)
164 mapped[:, self.__mask] = data
165
166 return mapped
167
168
170 """InShape is a shape of original mask"""
171 return self.__masksize
172
173
175 """OutSize is a number of non-0 elements in the mask"""
176 return self.__masknonzerosize
177
178
180 """By default returns a copy of the current mask.
181
182 If 'copy' is set to False a reference to the mask is returned instead.
183 This shared mask must not be modified!
184 """
185 if copy:
186 return self.__mask.copy()
187 else:
188 return self.__mask
189
190
192 """Returns a features coordinate in the original data space
193 for a given feature id.
194
195 If this method is called with a list of feature ids it returns a
196 2d-array where the first axis corresponds the dimensions in 'In'
197 dataspace and along the second axis are the coordinates of the features
198 on this dimension (like the output of NumPy.array.nonzero()).
199
200 XXX it might become __get_item__ access method
201
202 """
203
204
205 return N.array([self.__masknonzero[i][outId]
206 for i in xrange(self.__maskdim)])
207
208
210 """Returns a 2d array where each row contains the coordinate of the
211 feature with the corresponding id.
212 """
213 return N.transpose(self.__masknonzero)
214
215
219
220
222 """Translate a feature mask coordinate into a feature ID.
223 """
224
225
226
227 try:
228 tcoord = tuple(coord)
229 if self.__mask[tcoord] == 0:
230 raise ValueError, \
231 "The point %s didn't belong to the mask" % (`coord`)
232 return self.__forwardmap[tcoord]
233 except TypeError:
234 raise ValueError, \
235 "Coordinates %s are of incorrect dimension. " % `coord` + \
236 "The mask has %d dimensions." % self.__maskdim
237 except IndexError:
238 raise ValueError, \
239 "Coordinates %s are out of mask boundary. " % `coord` + \
240 "The mask is of %s shape." % `self.__mask.shape`
241
242
244 """Only listed outIds would remain.
245
246 *Function assumes that outIds are sorted*. In __debug__ mode selectOut
247 would check if obtained IDs are sorted and would warn the user if they
248 are not.
249
250 .. note::
251 If you feel strongly that you need to remap features
252 internally (ie to allow Ids with mixed order) please contact
253 developers of mvpa to discuss your use case.
254
255 The function used to accept a matrix-mask as the input but now
256 it really has to be a list of IDs
257
258 Feature/Bug:
259 * Negative outIds would not raise exception - just would be
260 treated 'from the tail'
261 """
262 if __debug__ and 'CHECK_SORTEDIDS' in debug.active:
263
264
265
266
267
268 if not isSorted(outIds):
269 warning("IDs for selectOut must be provided " +
270 "in sorted order, otherwise .forward() would fail"+
271 " on the data with multiple samples")
272
273
274 discarded = N.array([ True ] * self.nfeatures)
275 discarded[outIds] = False
276 discardedin = tuple(self.getInId(discarded))
277 self.__mask[discardedin] = False
278
279 self.__masknonzerosize = len(outIds)
280 self.__masknonzero = [ x[outIds] for x in self.__masknonzero ]
281
282
283
284
285
286 self.__forwardmap[self.__masknonzero] = \
287 N.arange(self.__masknonzerosize)
288
289
291 """Listed outIds would be discarded
292
293 """
294
295
296 discardedin = tuple(self.getInId(outIds))
297 self.__mask[discardedin] = False
298
299
300
301
302
303 self.__masknonzerosize -= len(outIds)
304 self.__masknonzero = [ N.delete(x, outIds)
305 for x in self.__masknonzero ]
306
307
308 self.__forwardmap[self.__masknonzero] = \
309 N.arange(self.__masknonzerosize)
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
329 """Returns a boolean mask with all features in `outIds` selected.
330
331 :Parameters:
332 outIds: list or 1d array
333 To be selected features ids in out-space.
334
335 :Returns:
336 ndarray: dtype='bool'
337 All selected features are set to True; False otherwise.
338 """
339 fmask = N.repeat(False, self.nfeatures)
340 fmask[outIds] = True
341
342 return fmask
343
344
346 """Returns a boolean mask with all features in `ouIds` selected.
347
348 This method works exactly like Mapper.convertOutIds2OutMask(), but the
349 feature mask is finally (reverse) mapped into in-space.
350
351 :Parameters:
352 outIds: list or 1d array
353 To be selected features ids in out-space.
354
355 :Returns:
356 ndarray: dtype='bool'
357 All selected features are set to True; False otherwise.
358 """
359 return self.reverse(self.convertOutIds2OutMask(outIds))
360
361
362
363 mask = property(fget=lambda self:self.getMask(False))
364
365
366
367
368
369
370