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
163
164 if __debug__ and self.nfeatures != data.shape[1]:
165 raise ValueError, \
166 "Cannot reverse map data of shape %s, whenever " \
167 "mask knows only %d features" \
168 % (data.shape, self.nfeatures)
169
170 mapped = N.zeros(data.shape[:1] + self.__mask.shape,
171 dtype=data.dtype)
172 mapped[:, self.__mask] = data
173
174 return mapped
175
176
178 """InShape is a shape of original mask"""
179 return self.__masksize
180
181
183 """OutSize is a number of non-0 elements in the mask"""
184 return self.__masknonzerosize
185
186
188 """By default returns a copy of the current mask.
189
190 If 'copy' is set to False a reference to the mask is returned instead.
191 This shared mask must not be modified!
192 """
193 if copy:
194 return self.__mask.copy()
195 else:
196 return self.__mask
197
198
200 """Returns a features coordinate in the original data space
201 for a given feature id.
202
203 If this method is called with a list of feature ids it returns a
204 2d-array where the first axis corresponds the dimensions in 'In'
205 dataspace and along the second axis are the coordinates of the features
206 on this dimension (like the output of NumPy.array.nonzero()).
207
208 XXX it might become __get_item__ access method
209
210 """
211
212
213 return N.array([self.__masknonzero[i][outId]
214 for i in xrange(self.__maskdim)])
215
216
218 """Returns a 2d array where each row contains the coordinate of the
219 feature with the corresponding id.
220 """
221 return N.transpose(self.__masknonzero)
222
223
227
228
230 """Translate a feature mask coordinate into a feature ID.
231 """
232
233
234
235 try:
236 tcoord = tuple(coord)
237 if self.__mask[tcoord] == 0:
238 raise ValueError, \
239 "The point %s didn't belong to the mask" % (`coord`)
240 return self.__forwardmap[tcoord]
241 except TypeError:
242 raise ValueError, \
243 "Coordinates %s are of incorrect dimension. " % `coord` + \
244 "The mask has %d dimensions." % self.__maskdim
245 except IndexError:
246 raise ValueError, \
247 "Coordinates %s are out of mask boundary. " % `coord` + \
248 "The mask is of %s shape." % `self.__mask.shape`
249
250
252 """Only listed outIds would remain.
253
254 *Function assumes that outIds are sorted*. In __debug__ mode selectOut
255 would check if obtained IDs are sorted and would warn the user if they
256 are not.
257
258 .. note::
259 If you feel strongly that you need to remap features
260 internally (ie to allow Ids with mixed order) please contact
261 developers of mvpa to discuss your use case.
262
263 The function used to accept a matrix-mask as the input but now
264 it really has to be a list of IDs
265
266 Feature/Bug:
267 * Negative outIds would not raise exception - just would be
268 treated 'from the tail'
269 """
270 if __debug__ and 'CHECK_SORTEDIDS' in debug.active:
271
272
273
274
275
276 if not isSorted(outIds):
277 warning("IDs for selectOut must be provided " +
278 "in sorted order, otherwise .forward() would fail"+
279 " on the data with multiple samples")
280
281
282 discarded = N.array([ True ] * self.nfeatures)
283 discarded[outIds] = False
284 discardedin = tuple(self.getInId(discarded))
285 self.__mask[discardedin] = False
286
287 self.__masknonzerosize = len(outIds)
288 self.__masknonzero = [ x[outIds] for x in self.__masknonzero ]
289
290
291
292
293
294 self.__forwardmap[self.__masknonzero] = \
295 N.arange(self.__masknonzerosize)
296
297
299 """Listed outIds would be discarded
300
301 """
302
303
304 discardedin = tuple(self.getInId(outIds))
305 self.__mask[discardedin] = False
306
307
308
309
310
311 self.__masknonzerosize -= len(outIds)
312 self.__masknonzero = [ N.delete(x, outIds)
313 for x in self.__masknonzero ]
314
315
316 self.__forwardmap[self.__masknonzero] = \
317 N.arange(self.__masknonzerosize)
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
337 """Returns a boolean mask with all features in `outIds` selected.
338
339 :Parameters:
340 outIds: list or 1d array
341 To be selected features ids in out-space.
342
343 :Returns:
344 ndarray: dtype='bool'
345 All selected features are set to True; False otherwise.
346 """
347 fmask = N.repeat(False, self.nfeatures)
348 fmask[outIds] = True
349
350 return fmask
351
352
354 """Returns a boolean mask with all features in `ouIds` selected.
355
356 This method works exactly like Mapper.convertOutIds2OutMask(), but the
357 feature mask is finally (reverse) mapped into in-space.
358
359 :Parameters:
360 outIds: list or 1d array
361 To be selected features ids in out-space.
362
363 :Returns:
364 ndarray: dtype='bool'
365 All selected features are set to True; False otherwise.
366 """
367 return self.reverse(self.convertOutIds2OutMask(outIds))
368
369
370
371 mask = property(fget=lambda self:self.getMask(False))
372
373
374
375
376
377
378