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.base import warning
17 from mvpa.support.copy import copy, deepcopy
18 from operator import isSequenceType
19
20 if __debug__:
21 from mvpa.base import debug
22
23
25 """Use path to file1 as the path to file2 is no absolute
26 path is given for file2
27
28 :Parameters:
29 force : bool
30 if True, force it even if the file2 starts with /
31 """
32 if not file2.startswith('/') or force:
33
34 return os.path.join(os.path.dirname(file1), file2.lstrip('/'))
35 else:
36 return file2
37
38
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 NOTE: implementation is broken for n>2
91 """
92
93 if n > 2:
94 raise ValueError, "_getUniqueLengthNCombinations_lt3 " \
95 "is broken for n>2, hence should not be used directly."
96
97
98 combos = []
99
100
101
102 def take(data, occupied, depth, taken):
103 for i, d in enumerate(data):
104
105 if occupied[i] == False:
106
107 if depth < n-1:
108
109 occupied[i] = True
110
111 take(data, occupied, depth+1, taken + [d])
112
113
114
115
116
117 else:
118
119 combos.append(taken + [d])
120
121
122 occupied = [False] * len(data)
123
124 take(data, occupied, 0, [])
125
126
127 return combos
128
130 """Generator of unique combinations form a list L of objects in
131 groups of size n.
132
133 # XXX EO: I guess they are already sorted.
134 # XXX EO: It seems to work well for n>20 :)
135
136 :Parameters:
137 L : list
138 list of unique ids
139 n : int
140 grouping size
141
142 Adopted from Li Daobing
143 http://code.activestate.com/recipes/190465/
144 (MIT license, according to activestate.com's policy)
145 """
146 if n==0: yield []
147 else:
148 for i in xrange(len(L)-n+1):
149 for cc in xuniqueCombinations(L[i+1:],n-1):
150 yield [L[i]]+cc
151
153 """Find all subsets of data
154
155 :Parameters:
156 L : list
157 list of unique ids
158 n : None or int
159 If None, all possible subsets are returned. If n is specified (int),
160 then only the ones of the length n are returned
161 sort : bool
162 Either to sort the resultant sequence
163
164 Adopted from Alex Martelli:
165 http://mail.python.org/pipermail/python-list/2001-January/067815.html
166 """
167 N = len(L)
168 if N > 20 and n == 1:
169 warning("getUniqueLengthNCombinations_binary should not be used for "
170 "large N")
171 result = []
172 for X in range(2**N):
173 x = [ L[i] for i in range(N) if X & (1L<<i) ]
174 if n is None or len(x) == n:
175
176 result.append(x)
177 result.sort()
178
179
180
181
182 return result
183
184
186 """Find all subsets of data
187
188 :Parameters:
189 L : list
190 list of unique ids
191 n : None or int
192 If None, all possible subsets are returned. If n is specified (int),
193 then only the ones of the length n are returned
194
195 TODO: work out single stable implementation -- probably just by fixing
196 _getUniqueLengthNCombinations_lt3
197 """
198 if n == 1:
199 return _getUniqueLengthNCombinations_lt3(L, n)
200 elif n==None:
201 return _getUniqueLengthNCombinations_binary(L, n, sort=True)
202 else:
203
204
205
206
207
208
209 return list(xuniqueCombinations(L, n))
210
211
213 """Find all subsets of data
214
215 :Parameters:
216 L : list
217 list of unique ids
218 n : None or int
219 If None, all possible subsets are returned. If n is specified (int),
220 then only the ones of the length n are returned
221 sort : bool
222 Either to sort the resultant sequence
223
224 Adopted from Alex Martelli:
225 http://mail.python.org/pipermail/python-list/2001-January/067815.html
226 """
227 N = len(L)
228 if N > 20 and n == 1:
229 warning("getUniqueLengthNCombinations_binary should not be used for "
230 "large N")
231 result = []
232 for X in range(2**N):
233 x = [ L[i] for i in range(N) if X & (1L<<i) ]
234 if n is None or len(x) == n:
235
236 result.append(x)
237 result.sort()
238
239
240
241
242 return result
243
244
246 """Find all subsets of data
247
248 :Parameters:
249 L : list
250 list of unique ids
251 n : None or int
252 If None, all possible subsets are returned. If n is specified (int),
253 then only the ones of the length n are returned
254
255 TODO: work out single stable implementation -- probably just by fixing
256 _getUniqueLengthNCombinations_lt3
257 """
258 if n == 1:
259 return _getUniqueLengthNCombinations_lt3(L, n)
260 else:
261 return _getUniqueLengthNCombinations_binary(L, n, sort=True)
262
263
265 """Given a `value` returns a string where each line is indented
266
267 Needed for a cleaner __repr__ output
268 `v` - arbitrary
269 """
270 return re.sub('\n', '\n ', str(v))
271
272
274 """Craft unique id+hash for an object
275 """
276 res = "%s" % id(val)
277 if isinstance(val, list):
278 val = tuple(val)
279 try:
280 res += ":%s" % hash(buffer(val))
281 except:
282 try:
283 res += ":%s" % hash(val)
284 except:
285 pass
286 pass
287 return res
288
290 """Check if listed items are in sorted order.
291
292 :Parameters:
293 `items`: iterable container
294
295 :return: `True` if were sorted. Otherwise `False` + Warning
296 """
297 items_sorted = deepcopy(items)
298 items_sorted.sort()
299 equality = items_sorted == items
300
301 if hasattr(equality, '__iter__'):
302 equality = N.all(equality)
303 return equality
304
305
307 """For given coord check if it is within a specified volume size.
308
309 Returns True/False. Assumes that volume coordinates start at 0.
310 No more generalization (arbitrary minimal coord) is done to save
311 on performance
312 """
313 for i in xrange(len(coord)):
314 if coord[i] < 0 or coord[i] >= shape[i]:
315 return False
316 return True
317
318
320 """Return a list of break points.
321
322 :Parameters:
323 items : iterable
324 list of items, such as chunks
325 contiguous : bool
326 if `True` (default) then raise Value Error if items are not
327 contiguous, i.e. a label occur in multiple contiguous sets
328
329 :raises: ValueError
330
331 :return: list of indexes for every new set of items
332 """
333 prev = None
334 known = []
335 """List of items which was already seen"""
336 result = []
337 """Resultant list"""
338 for index in xrange(len(items)):
339 item = items[index]
340 if item in known:
341 if index > 0:
342 if prev != item:
343 if contiguous:
344 raise ValueError, \
345 "Item %s was already seen before" % str(item)
346 else:
347 result.append(index)
348 else:
349 known.append(item)
350 result.append(index)
351 prev = item
352 return result
353
354
355 -def RFEHistory2maps(history):
356 """Convert history generated by RFE into the array of binary maps
357
358 Example:
359 history2maps(N.array( [ 3,2,1,0 ] ))
360 results in
361 array([[ 1., 1., 1., 1.],
362 [ 1., 1., 1., 0.],
363 [ 1., 1., 0., 0.],
364 [ 1., 0., 0., 0.]])
365 """
366
367
368 history = N.array(history)
369 nfeatures, steps = len(history), max(history) - min(history) + 1
370 history_maps = N.zeros((steps, nfeatures))
371
372 for step in xrange(steps):
373 history_maps[step, history >= step] = 1
374
375 return history_maps
376
377
379 """Compute some overlap stats from a sequence of binary maps.
380
381 When called with a sequence of binary maps (e.g. lists or arrays) the
382 fraction of mask elements that are non-zero in a customizable proportion
383 of the maps is returned. By default this threshold is set to 1.0, i.e.
384 such an element has to be non-zero in *all* maps.
385
386 Three additional maps (same size as original) are computed:
387
388 * overlap_map: binary map which is non-zero for each overlapping element.
389 * spread_map: binary map which is non-zero for each element that is
390 non-zero in any map, but does not exceed the overlap
391 threshold.
392 * ovstats_map: map of float with the raw elementwise fraction of overlap.
393
394 All maps are available via class members.
395 """
396 - def __init__(self, overlap_threshold=1.0):
397 """Nothing to be seen here.
398 """
399 self.__overlap_threshold = overlap_threshold
400
401
402 self.overlap_map = None
403 self.spread_map = None
404 self.ovstats_map = None
405
406
408 """Returns fraction of overlapping elements.
409 """
410 ovstats = N.mean(maps, axis=0)
411
412 self.overlap_map = (ovstats >= self.__overlap_threshold )
413 self.spread_map = N.logical_and(ovstats > 0.0,
414 ovstats < self.__overlap_threshold)
415 self.ovstats_map = ovstats
416
417 return N.mean(ovstats >= self.__overlap_threshold)
418
419
421 """Simple class to define properties of an event.
422
423 The class is basically a dictionary. Any properties can
424 be passed as keyword arguments to the constructor, e.g.:
425
426 >>> ev = Event(onset=12, duration=2.45)
427
428 Conventions for keys:
429
430 `onset`
431 The onset of the event in some unit.
432 `duration`
433 The duration of the event in the same unit as `onset`.
434 `label`
435 E.g. the condition this event is part of.
436 `chunk`
437 Group this event is part of (if any), e.g. experimental run.
438 `features`
439 Any amount of additional features of the event. This might include
440 things like physiological measures, stimulus intensity. Must be a mutable
441 sequence (e.g. list), if present.
442 """
443 _MUSTHAVE = ['onset']
444
446
447 dict.__init__(self, **kwargs)
448
449
450 for k in Event._MUSTHAVE:
451 if not self.has_key(k):
452 raise ValueError, "Event must have '%s' defined." % k
453
454
456 """Convert `onset` and `duration` information into descrete timepoints.
457
458 :Parameters:
459 dt: float
460 Temporal distance between two timepoints in the same unit as `onset`
461 and `duration`.
462 storeoffset: bool
463 If True, the temporal offset between original `onset` and
464 descretized `onset` is stored as an additional item in `features`.
465
466 :Return:
467 A copy of the original `Event` with `onset` and optionally `duration`
468 replaced by their corresponding descrete timepoint. The new onset will
469 correspond to the timepoint just before or exactly at the original
470 onset. The new duration will be the number of timepoints covering the
471 event from the computed onset timepoint till the timepoint exactly at
472 the end, or just after the event.
473
474 Note again, that the new values are expressed as #timepoint and not
475 in their original unit!
476 """
477 dt = float(dt)
478 onset = self['onset']
479 out = deepcopy(self)
480
481
482 out['onset'] = int(N.floor(onset / dt))
483
484 if storeoffset:
485
486 offset = onset - (out['onset'] * dt)
487
488 if out.has_key('features'):
489 out['features'].append(offset)
490 else:
491 out['features'] = [offset]
492
493 if out.has_key('duration'):
494
495
496 out['duration'] = int(N.ceil((onset + out['duration']) / dt) \
497 - out['onset'])
498
499 return out
500
501
502
504 - def __init__(self, call, attribs=None, argfilter=None, expand_args=True,
505 copy_attribs=True):
506 """Initialize
507
508 :Parameters:
509 expand_args : bool
510 Either to expand the output of looper into a list of arguments for
511 call
512 attribs : list of basestr
513 What attributes of call to store and return later on?
514 copy_attribs : bool
515 Force copying values of attributes
516 """
517
518 self.call = call
519 """Call which gets called in the harvester."""
520
521 if attribs is None:
522 attribs = []
523 if not isSequenceType(attribs):
524 raise ValueError, "'attribs' have to specified as a sequence."
525
526 if not (argfilter is None or isSequenceType(argfilter)):
527 raise ValueError, "'argfilter' have to be a sequence or None."
528
529
530 self.argfilter = argfilter
531 self.expand_args = expand_args
532 self.copy_attribs = copy_attribs
533 self.attribs = attribs
534
535
536
538 """World domination helper: do whatever it is asked and accumulate results
539
540 XXX Thinks about:
541 - Might we need to deepcopy attributes values?
542 - Might we need to specify what attribs to copy and which just to bind?
543 """
544
545 - def __init__(self, source, calls, simplify_results=True):
546 """Initialize
547
548 :Parameters:
549 source
550 Generator which produce food for the calls.
551 calls : sequence of HarvesterCall instances
552 Calls which are processed in the loop. All calls are processed in
553 order of apperance in the sequence.
554 simplify_results: bool
555 Remove unecessary overhead in results if possible (nested lists
556 and dictionaries).
557 """
558 if not isSequenceType(calls):
559 raise ValueError, "'calls' have to specified as a sequence."
560
561 self.__source = source
562 """Generator which feeds the harvester"""
563
564 self.__calls = calls
565 """Calls which gets called with each generated source"""
566
567 self.__simplify_results = simplify_results
568
569
571 """
572 """
573
574
575 results = [dict([('result', [])] + [(a, []) for a in c.attribs]) \
576 for c in self.__calls]
577
578
579 for (i, X) in enumerate(self.__source(*args, **kwargs)):
580 for (c, call) in enumerate(self.__calls):
581
582 if i == 0 and call.expand_args and not isSequenceType(X):
583 raise RuntimeError, \
584 "Cannot expand non-sequence result from %s" % \
585 `self.__source`
586
587
588 if call.argfilter:
589 filtered_args = [X[f] for f in call.argfilter]
590 else:
591 filtered_args = X
592
593 if call.expand_args:
594 result = call.call(*filtered_args)
595 else:
596 result = call.call(filtered_args)
597
598
599
600
601
602
603
604 results[c]['result'].append(result)
605
606 for attrib in call.attribs:
607 attrv = call.call.__getattribute__(attrib)
608
609 if call.copy_attribs:
610 attrv = copy(attrv)
611
612 results[c][attrib].append(attrv)
613
614
615 if self.__simplify_results:
616
617 for (c, call) in enumerate(self.__calls):
618 if not len(call.attribs):
619 results[c] = results[c]['result']
620
621 if len(self.__calls) == 1:
622 results = results[0]
623
624 return results
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642