1
2
3
4
5
6
7
8
9 """Support function -- little helpers in everyday life"""
10
11 __docformat__ = 'restructuredtext'
12
13 import numpy as N
14 import re, os
15
16 from mvpa.misc.copy import copy, deepcopy
17 from operator import isSequenceType
18
19 if __debug__:
20 from mvpa.base import debug
21
22
24 """Use path to file1 as the path to file2 is no absolute
25 path is given for file2
26
27 :Parameters:
28 force : bool
29 if True, force it even if the file2 starts with /
30 """
31 if not file2.startswith('/') or force:
32
33 return os.path.join(os.path.dirname(file1), file2.lstrip('/'))
34 else:
35 return file2
36
37
75
76
77
79 """Generates a list of lists containing all combinations of
80 elements of data of length 'n' without repetitions.
81
82 data: list
83 n: integer
84
85 This function is adapted from a Java version posted in some forum on
86 the web as an answer to the question 'How can I generate all possible
87 combinations of length n?'. Unfortunately I cannot remember which
88 forum it was.
89 """
90
91
92 combos = []
93
94
95
96 def take(data, occupied, depth, taken):
97 for i, d in enumerate(data):
98
99 if occupied[i] == False:
100
101 if depth < n-1:
102
103 occupied[i] = True
104
105 take(data, occupied, depth+1, taken + [d])
106
107
108
109
110
111 else:
112
113 combos.append(taken + [d])
114
115
116 occupied = [False] * len(data)
117
118 take(data, occupied, 0, [])
119
120
121 return combos
122
123
125 """Given a `value` returns a string where each line is indented
126
127 Needed for a cleaner __repr__ output
128 `v` - arbitrary
129 """
130 return re.sub('\n', '\n ', str(v))
131
132
134 """Craft unique id+hash for an object
135 """
136 res = "%s" % id(val)
137 if isinstance(val, list):
138 val = tuple(val)
139 try:
140 res += ":%s" % hash(buffer(val))
141 except:
142 try:
143 res += ":%s" % hash(val)
144 except:
145 pass
146 pass
147 return res
148
150 """Check if listed items are in sorted order.
151
152 :Parameters:
153 `items`: iterable container
154
155 :return: `True` if were sorted. Otherwise `False` + Warning
156 """
157 itemsOld = deepcopy(items)
158 items.sort()
159 equality = itemsOld == items
160
161 if hasattr(equality, '__iter__'):
162 equality = N.all(equality)
163 return equality
164
165
167 """For given coord check if it is within a specified volume size.
168
169 Returns True/False. Assumes that volume coordinates start at 0.
170 No more generalization (arbitrary minimal coord) is done to save
171 on performance
172 """
173 for i in xrange(len(coord)):
174 if coord[i] < 0 or coord[i] >= shape[i]:
175 return False
176 return True
177
178
180 """Return a list of break points.
181
182 :Parameters:
183 items : iterable
184 list of items, such as chunks
185 contiguous : bool
186 if `True` (default) then raise Value Error if items are not
187 contiguous, i.e. a label occur in multiple contiguous sets
188
189 :raises: ValueError
190
191 :return: list of indexes for every new set of items
192 """
193 prev = None
194 known = []
195 """List of items which was already seen"""
196 result = []
197 """Resultant list"""
198 for index in xrange(len(items)):
199 item = items[index]
200 if item in known:
201 if index > 0:
202 if prev != item:
203 if contiguous:
204 raise ValueError, \
205 "Item %s was already seen before" % str(item)
206 else:
207 result.append(index)
208 else:
209 known.append(item)
210 result.append(index)
211 prev = item
212 return result
213
214
215 -def RFEHistory2maps(history):
216 """Convert history generated by RFE into the array of binary maps
217
218 Example:
219 history2maps(N.array( [ 3,2,1,0 ] ))
220 results in
221 array([[ 1., 1., 1., 1.],
222 [ 1., 1., 1., 0.],
223 [ 1., 1., 0., 0.],
224 [ 1., 0., 0., 0.]])
225 """
226
227
228 history = N.array(history)
229 nfeatures, steps = len(history), max(history) - min(history) + 1
230 history_maps = N.zeros((steps, nfeatures))
231
232 for step in xrange(steps):
233 history_maps[step, history >= step] = 1
234
235 return history_maps
236
237
239 """Compute some overlap stats from a sequence of binary maps.
240
241 When called with a sequence of binary maps (e.g. lists or arrays) the
242 fraction of mask elements that are non-zero in a customizable proportion
243 of the maps is returned. By default this threshold is set to 1.0, i.e.
244 such an element has to be non-zero in *all* maps.
245
246 Three additional maps (same size as original) are computed:
247
248 * overlap_map: binary map which is non-zero for each overlapping element.
249 * spread_map: binary map which is non-zero for each element that is
250 non-zero in any map, but does not exceed the overlap
251 threshold.
252 * ovstats_map: map of float with the raw elementwise fraction of overlap.
253
254 All maps are available via class members.
255 """
256 - def __init__(self, overlap_threshold=1.0):
257 """Nothing to be seen here.
258 """
259 self.__overlap_threshold = overlap_threshold
260
261
262 self.overlap_map = None
263 self.spread_map = None
264 self.ovstats_map = None
265
266
268 """Returns fraction of overlapping elements.
269 """
270 ovstats = N.mean(maps, axis=0)
271
272 self.overlap_map = (ovstats >= self.__overlap_threshold )
273 self.spread_map = N.logical_and(ovstats > 0.0,
274 ovstats < self.__overlap_threshold)
275 self.ovstats_map = ovstats
276
277 return N.mean(ovstats >= self.__overlap_threshold)
278
279
281 """Simple class to define properties of an event.
282
283 The class is basically a dictionary. Any properties can
284 be pass as keyword arguments to the constructor, e.g.:
285
286 >>> ev = Event(onset=12, duration=2.45)
287
288 Conventions for keys:
289
290 `onset`
291 The onset of the event in some unit.
292 `duration`
293 The duration of the event in the same unit as `onset`.
294 `label`
295 E.g. the condition this event is part of.
296 `chunk`
297 Group this event is part of (if any), e.g. experimental run.
298 `features`
299 Any amount of additional features of the event. This might include
300 things like physiological measures, stimulus intensity. Must be a mutable
301 sequence (e.g. list), if present.
302 """
303 _MUSTHAVE = ['onset']
304
306
307 dict.__init__(self, **kwargs)
308
309
310 for k in Event._MUSTHAVE:
311 if not self.has_key(k):
312 raise ValueError, "Event must have '%s' defined." % k
313
314
316 """Convert `onset` and `duration` information into descrete timepoints.
317
318 :Parameters:
319 dt: float
320 Temporal distance between two timepoints in the same unit as `onset`
321 and `duration`.
322 storeoffset: bool
323 If True, the temporal offset between original `onset` and
324 descretized `onset` is stored as an additional item in `features`.
325
326 :Return:
327 A copy of the original `Event` with `onset` and optionally `duration`
328 replaced by their corresponding descrete timepoint. The new onset will
329 correspond to the timepoint just before or exactly at the original
330 onset. The new duration will be the number of timepoints covering the
331 event from the computed onset timepoint till the timepoint exactly at
332 the end, or just after the event.
333
334 Note again, that the new values are expressed as #timepoint and not
335 in their original unit!
336 """
337 dt = float(dt)
338 onset = self['onset']
339 out = deepcopy(self)
340
341
342 out['onset'] = int(N.floor(onset / dt))
343
344 if storeoffset:
345
346 offset = onset - (out['onset'] * dt)
347
348 if out.has_key('features'):
349 out['features'].append(offset)
350 else:
351 out['features'] = [offset]
352
353 if out.has_key('duration'):
354
355
356 out['duration'] = int(N.ceil((onset + out['duration']) / dt) \
357 - out['onset'])
358
359 return out
360
361
362
364 - def __init__(self, call, attribs=None, argfilter=None, expand_args=True,
365 copy_attribs=True):
366 """Initialize
367
368 :Parameters:
369 expand_args : bool
370 Either to expand the output of looper into a list of arguments for
371 call
372 attribs : list of basestr
373 What attributes of call to store and return later on?
374 copy_attribs : bool
375 Force copying values of attributes
376 """
377
378 self.call = call
379 """Call which gets called in the harvester."""
380
381 if attribs is None:
382 attribs = []
383 if not isSequenceType(attribs):
384 raise ValueError, "'attribs' have to specified as a sequence."
385
386 if not (argfilter is None or isSequenceType(argfilter)):
387 raise ValueError, "'argfilter' have to be a sequence or None."
388
389
390 self.argfilter = argfilter
391 self.expand_args = expand_args
392 self.copy_attribs = copy_attribs
393 self.attribs = attribs
394
395
396
398 """World domination helper: do whatever it is asked and accumulate results
399
400 XXX Thinks about:
401 - Might we need to deepcopy attributes values?
402 - Might we need to specify what attribs to copy and which just to bind?
403 """
404
405 - def __init__(self, source, calls, simplify_results=True):
406 """Initialize
407
408 :Parameters:
409 source
410 Generator which produce food for the calls.
411 calls : sequence of HarvesterCall instances
412 Calls which are processed in the loop. All calls are processed in
413 order of apperance in the sequence.
414 simplify_results: bool
415 Remove unecessary overhead in results if possible (nested lists
416 and dictionaries).
417 """
418 if not isSequenceType(calls):
419 raise ValueError, "'calls' have to specified as a sequence."
420
421 self.__source = source
422 """Generator which feeds the harvester"""
423
424 self.__calls = calls
425 """Calls which gets called with each generated source"""
426
427 self.__simplify_results = simplify_results
428
429
431 """
432 """
433
434
435 results = [dict([('result', [])] + [(a, []) for a in c.attribs]) \
436 for c in self.__calls]
437
438
439 for (i, X) in enumerate(self.__source(*args, **kwargs)):
440 for (c, call) in enumerate(self.__calls):
441
442 if i == 0 and call.expand_args and not isSequenceType(X):
443 raise RuntimeError, \
444 "Cannot expand non-sequence result from %s" % \
445 `self.__source`
446
447
448 if call.argfilter:
449 filtered_args = [X[f] for f in call.argfilter]
450 else:
451 filtered_args = X
452
453 if call.expand_args:
454 result = call.call(*filtered_args)
455 else:
456 result = call.call(filtered_args)
457
458
459
460
461
462
463
464 results[c]['result'].append(result)
465
466 for attrib in call.attribs:
467 attrv = call.call.__getattribute__(attrib)
468
469 if call.copy_attribs:
470 attrv = copy(attrv)
471
472 results[c][attrib].append(attrv)
473
474
475 if self.__simplify_results:
476
477 for (c, call) in enumerate(self.__calls):
478 if not len(call.attribs):
479 results[c] = results[c]['result']
480
481 if len(self.__calls) == 1:
482 results = results[0]
483
484 return results
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502