source: orange/orange/orng2Array.py @ 6538:a5f65d7f0b2c

Revision 6538:a5f65d7f0b2c, 23.6 KB checked in by Mitar <Mitar@…>, 4 years ago (diff)

Made XPM version of the icon 32x32.

Line 
1# ORANGE Domain Translation
2#    by Alex Jakulin (jakulin@acm.org)
3#
4# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
5#
6# CVS Status: $Id$
7#
8# Version 2.0 (20/11/2003)
9# *DISABLED*  - SVM no longer uses -1 for minimum value in categorical attributes
10#
11# Version 1.9 (17/11/2003)
12#   - Dummy picks the most frequent value to be the default
13#   - added standardization
14#   - fixed a bug in binarization
15#
16# Version 1.8 (10/7/2003)
17#   - support for domain disparity
18#
19# Version 1.7 (14/8/2002)
20#   - status functions
21#   - Support for setting the preferred ordinal attribute transformation.
22#
23# Version 1.6 (31/10/2001)
24#
25#
26# TO DO:
27#   - Currently dummy variables are created for all discrete attributes, but
28#     often it would be more desirable, especially for ordinal attributes,
29#     to have other methods of creating continuous attributes. Perhaps use
30#     MDS for this.
31#
32
33import orange, warnings, math
34
35#
36# MAPPERS translate orange domains into primitive python arrays used by the
37#                                                           LR/SVM functions
38#
39# note that these mappings are extremely SLOW and INEFFICIENT
40#
41#
42# Scalizer maps continuous input into an interval ([-1,1] for SVM, [0,1] for LR)
43# Ordinalizer maps is a scalizer for ordinal attributes, assuming even spread.
44#
45# Binarizer and Dummy create several new attributes:
46# Binarizer maps like this <1,2,3> -> <[1,0,0],[0,1,0],[0,0,1]>
47# Dummy maps like this <1,2,3> -> <[1,0],[0,1],[0,0]>
48#
49# The default operators are scalizer (cont) and dummy (discr);
50# For class, ordinalizer is used instead of dummy
51#
52# if you are unhappy about this, subclass DomainTranslation and fudge with analyse()
53
54SVM_CMIN = -1.0
55SVM_MIN = 0.0
56SVM_MAX = 1.0
57
58def _getattr(ex,attr):
59    # a 'smart' function tries to access an attribute first by reference, then by name
60    spec = 0
61    try:
62        v = ex[attr]
63    except:
64        v = attr.computeValue(ex)
65        if v.isSpecial():
66            # perhaps the domain changed
67            try:
68                v = ex[attr.name]
69            except:
70                warnings.warn("Missing attribute %s"%attr.name)
71                v = attr.values[0]
72                spec = 1           
73    if spec == 0:
74        try:
75            spec = v.isSpecial()
76        except:
77            pass
78    return (v,spec)
79
80
81
82class Gaussianizer:
83    def __init__(self,idx,attr,isclass=0,n_bells=3):
84        assert(n_bells >= 1)
85        self.n_bells = n_bells
86        self.idx = idx
87        self.nidx = idx+n_bells
88        self.attr= attr
89        self.values = []
90        self.isclass = isclass
91
92    def learn(self,value):
93        try:
94            spec = value.isSpecial()
95        except:
96            spec = 0
97        if not spec:
98            value = float(value)
99            self.values.append(value)
100
101    def activate(self):
102        pass
103
104    def status(self):
105        print "Gaussianizer: "
106        print "\tattr:",self.attr
107        print "\tidx: ",self.idx,'-',self.nidx
108        print '\t',
109        for i in xrange(self.n_bells):
110            print '%f(%f)'%(self.avg[i],self.std[i]),
111        print
112           
113    def prep(self):
114        self.values.sort()
115        l = self.values
116        m = len(l)/self.n_bells
117        if m == 0:
118            self.n_bells = 1
119            m = len(l)
120        sets = [l[:m]]
121        if self.n_bells > 2:
122            for x in xrange(1,self.n_bells-1):
123                sets.append(l[m*x:m*(x+1)])
124        sets.append(l[m*(self.n_bells-1):])
125        self.avg = []
126        self.std = []
127        for s in sets:
128            if len(s) > 2:
129                mmin = min(s)
130                mmax = max(s)
131                if mmin < mmax:
132                    self.avg.append((mmax+mmin)/2.0)
133                    #self.std.append(-2/(mmax-mmin))      # vee-shaped basis
134                    self.std.append(-4/((mmax-mmin)**2)) # gaussian basis
135        self.n_bells = len(self.avg)
136        #self.invert = 1.0/self.n_bells
137        self.invert = 1.0
138
139    def prepareSVM(self,nomo):
140        self.prep()
141        if self.isclass == 1:
142            self.missing = 3.14159   # a special value!
143        else:
144            self.missing = (0,1)
145
146    def prepareLR(self):
147        self.prep()
148        self.missing = 0.0
149       
150    def apply(self,ex,list):
151        (value,spec) = _getattr(ex,self.attr)
152        if spec:
153            for i in xrange(self.n_bells):
154                list[self.idx+i] = self.missing
155        else:
156            fv = float(value)
157            for i in xrange(self.n_bells):
158                v = abs(fv-self.avg[i])
159                v *= v # gaussian basis
160                list[self.idx+i] = self.invert*math.exp(v*self.std[i])
161        return
162
163    def descript(self):
164        return ['%s'%(self.attr.name)]
165
166    def description(self):
167        return (0,'%s'%(self.attr.name))
168
169    def inverse(self,list):
170        raise "Gaussianizer is not (yet) invertible."
171
172
173class Scalizer:
174    def __init__(self,idx,attr,isclass=0):
175        self.min = 1e200
176        self.max = -1e200
177        self.idx = idx
178        self.nidx = idx+1
179        self.attr= attr
180        self.isclass = isclass
181
182    def learn(self,value):
183        try:
184            spec = value.isSpecial()
185        except:
186            spec = 0
187        if not spec:
188            value = float(value)
189            if self.min > value:
190                self.min = value
191            if self.max < value:
192                self.max = value
193
194    def status(self):
195        print "scalizer: "
196        print "\tattr:",self.attr
197        print "\tidx: ",self.idx
198        print "\tmin: ",self.min
199        print "\tmax: ",self.max
200           
201    def prepareSVM(self,nomo):
202        if self.isclass == 1:
203            self.missing = 3.14159   # a special value!
204        else:
205            self.missing = (0,1)
206        if self.min == self.max:
207            if self.max > 0.0 or self.max < 0.0 :
208                self.mult = 1.0/self.max
209                self.disp = 0.0
210            else:
211                self.mult = 1.0
212                self.disp = self.max
213        else:
214            self.mult = 2.0/(self.max-self.min)
215            self.disp = (self.min+self.max)/2.0
216
217    def activate(self):
218        pass
219
220    def prepareLR(self):
221        self.missing = (self.min+self.max)/2
222        self.mult = 1
223        self.disp = 0
224       
225    def apply(self,ex,list):
226        (value,spec) = _getattr(ex,self.attr)
227        if spec:
228            list[self.idx] = self.missing
229        else:
230            list[self.idx] = (float(value)-self.disp)*self.mult
231        return
232
233    def description(self):
234        if self.disp==0.0 and self.mult==1:
235            return ['%s'%(self.attr.name)]
236        else:
237            return ['%s(x)=(x-%f)*%f'%(self.attr.name,self.disp,self.mult)]
238
239    def description(self):
240        return (0,'%s'%(self.attr.name))
241
242    def inverse(self,list):
243        return self.attr((list[self.idx]/self.mult)+self.disp)
244       
245
246
247class Quadratizer:
248    def __init__(self,idx,attr,isclass=0):
249        self.avg = 0.0
250        self.stddev = 0.0
251        self.idx = idx
252        self.nidx = idx+2
253        self.attr= attr
254        self.values = []
255        self.isclass = isclass
256
257    def learn(self,value):
258        try:
259            spec = value.isSpecial()
260        except:
261            spec = 0
262        if not spec:
263            value = float(value)
264            self.values.append(value)
265            self.avg += value
266
267    def activate(self):
268        pass
269
270    def status(self):
271        print "quadratizer: "
272        print "\tattr:",self.attr
273        print "\tidx: ",self.idx
274        print "\taverage: ",self.avg
275        print "\tstddev: ",self.stddev
276           
277    def prep(self):
278        self.avg /= len(self.values)
279        for x in self.values:
280            t = x-self.avg
281            self.stddev += t*t
282        self.stddev /= len(self.values)-1
283        self.stddev = math.sqrt(self.stddev)
284        self.mult = 0.5/self.stddev
285        self.disp = self.avg
286        mmin = min(self.values)
287        mmax = max(self.values)
288        self.correction = 0.5/max((self.mult*(mmin-self.disp))**2, (self.mult*(mmax-self.disp))**2)
289       
290
291    def prepareSVM(self,nomo):
292        self.prep()
293        if self.isclass == 1:
294            self.missing = 3.14159   # a special value!
295        else:
296            self.missing = (0,1)
297
298    def prepareLR(self):
299        self.prep()
300        self.mult = 1.0
301        self.missing = 0.0
302       
303    def apply(self,ex,list):
304        (value,spec) = _getattr(ex,self.attr)
305        if spec:
306            list[self.idx] = self.missing
307            list[self.idx+1] = self.missing
308        else:
309            list[self.idx] = (float(value)-self.disp)*self.mult
310            list[self.idx+1] = (list[self.idx]**2)*self.correction
311        return
312
313    def descript(self):
314        if self.disp==0.0 and self.mult==1.0:
315            return ['%s'%(self.attr.name)]
316        else:
317            return ['(%s-%f)*%f'%(self.attr.name,self.disp,self.mult)]
318
319    def description(self):
320        return (0,'%s'%(self.attr.name))
321
322    def inverse(self,list):
323        return self.attr((list[self.idx]/self.mult)+self.disp)
324
325class Cubizer:
326    def __init__(self,idx,attr,isclass=0):
327        self.avg = 0.0
328        self.stddev = 0.0
329        self.idx = idx
330        self.nidx = idx+3
331        self.attr= attr
332        self.values = []
333        self.isclass = isclass
334
335    def learn(self,value):
336        try:
337            spec = value.isSpecial()
338        except:
339            spec = 0
340        if not spec:
341            value = float(value)
342            self.values.append(value)
343            self.avg += value
344
345    def activate(self):
346        pass
347
348    def status(self):
349        print "cubizer: "
350        print "\tattr:",self.attr
351        print "\tidx: ",self.idx
352        print "\taverage: ",self.avg
353        print "\tstddev: ",self.stddev
354           
355    def prep(self):
356        self.avg /= len(self.values)
357        for x in self.values:
358            t = x-self.avg
359            self.stddev += t*t
360        self.stddev /= len(self.values)-1
361        self.stddev = math.sqrt(self.stddev)
362        self.mult = 0.33/self.stddev
363        self.disp = self.avg
364        mmin = min(self.values)
365        mmax = max(self.values)
366        self.correction2 = 0.33/max((self.mult*(mmin-self.disp))**2, (self.mult*(mmax-self.disp))**2)
367        self.correction3 = 0.33/max((self.mult*(mmin-self.disp))**3, (self.mult*(mmax-self.disp))**3)
368       
369
370    def prepareSVM(self,nomo):
371        self.prep()
372        if self.isclass == 1:
373            self.missing = 3.14159   # a special value!
374        else:
375            self.missing = (0,1)
376
377    def prepareLR(self):
378        self.prep()
379        self.mult = 1.0
380        self.missing = 0.0
381       
382    def apply(self,ex,list):
383        (value,spec) = _getattr(ex,self.attr)
384        if spec:
385            list[self.idx] = self.missing
386            list[self.idx+1] = self.missing
387            list[self.idx+2] = self.missing
388        else:
389            list[self.idx] = (float(value)-self.disp)*self.mult
390            list[self.idx+1] = (list[self.idx]**2)*self.correction2
391            list[self.idx+2] = (list[self.idx]**3)*self.correction3
392        return
393
394    def descript(self):
395        if self.disp==0.0 and self.mult==1.0:
396            return ['%s'%(self.attr.name)]
397        else:
398            return ['(%s-%f)*%f'%(self.attr.name,self.disp,self.mult)]
399
400    def description(self):
401        return (0,'%s'%(self.attr.name))
402
403    def inverse(self,list):
404        return self.attr((list[self.idx]/self.mult)+self.disp)
405
406
407class Standardizer:
408    def __init__(self,idx,attr,isclass=0):
409        self.avg = 0.0
410        self.stddev = 0.0
411        self.idx = idx
412        self.nidx = idx+1
413        self.attr= attr
414        self.values = []
415        self.isclass = isclass
416
417    def learn(self,value):
418        try:
419            spec = value.isSpecial()
420        except:
421            spec = 0
422        if not spec:
423            value = float(value)
424            self.values.append(value)
425            self.avg += value
426
427    def activate(self):
428        pass
429
430    def status(self):
431        print "standardizer: "
432        print "\tattr:",self.attr
433        print "\tidx: ",self.idx
434        print "\taverage: ",self.avg
435        print "\tstddev: ",self.stddev
436           
437    def prep(self):
438        self.avg /= len(self.values)
439        for x in self.values:
440            t = x-self.avg
441            self.stddev += t*t
442        self.stddev /= len(self.values)-1
443        self.stddev = math.sqrt(self.stddev)
444    if self.stddev > 1e-6:
445        self.mult = 1.0/self.stddev
446    else:
447        self.mult = 1.0
448        self.disp = self.avg
449
450    def prepareSVM(self,nomo):
451        self.prep()
452        if self.isclass == 1:
453            self.missing = 3.14159   # a special value!
454        else:
455            self.missing = (0,1)
456
457    def prepareLR(self):
458        self.prep()
459        self.mult = 1.0
460        self.missing = 0.0
461       
462    def apply(self,ex,list):
463        (value,spec) = _getattr(ex,self.attr)
464        if spec:
465            list[self.idx] = self.missing
466        else:
467            list[self.idx] = (float(value)-self.disp)*self.mult
468        return
469
470    def descript(self):
471        if self.disp==0.0 and self.mult==1.0:
472            return ['%s'%(self.attr.name)]
473        else:
474            return ['(%s-%f)*%f'%(self.attr.name,self.disp,self.mult)]
475
476    def description(self):
477        return (0,'%s'%(self.attr.name))
478
479    def inverse(self,list):
480        return self.attr((list[self.idx]/self.mult)+self.disp)
481
482
483class Ordinalizer:
484    def __init__(self,idx,attr,isclass=0):
485        self.idx = idx
486        self.nidx = idx+1
487        self.min = 0.0
488        self.max = len(attr.values)-1.0
489        self.attr = attr
490        self.isclass = isclass
491        return
492
493    def status(self):
494        print "ordinalizer: "
495        print "\tattr:",self.attr
496        print "\tidx: ",self.idx
497        print "\tmin: ",self.min
498        print "\tmax: ",self.max
499        print "\tisclass:",self.isclass
500           
501    def learn(self,value):
502        return
503           
504    def activate(self):
505        pass
506
507    def prepareSVM(self,nomo):
508        if self.isclass==1:
509            # keep all classes integer, because libsvm does rounding!!!
510            self.mult = 1.0
511            self.disp = 0.0
512        else:
513            self.missing = (0,1)
514            if self.min == self.max:
515                if self.max > 0.0 or self.max < 0.0 :
516                    self.mult = 1.0/self.max
517                    self.disp = 0.0
518                else:
519                    self.mult = 1.0
520                    self.disp = self.max
521            else:
522                self.mult = 2.0/(self.max-self.min)
523                self.disp = (self.min+self.max)/2.0
524        return
525
526    def prepareLR(self):
527        self.missing = 0.5
528        if self.min == self.max:
529            if self.max > 0.0 or self.max < 0.0 :
530                self.mult = 1.0/self.max
531                self.disp = 0.0
532            else:
533                self.mult = 1.0
534                self.disp = self.max
535        else:
536            self.mult = 1.0/(self.max-self.min)
537            self.disp = self.min
538        return
539       
540    def apply(self,ex,list):
541        (value,spec) = _getattr(ex,self.attr)
542        if spec:
543            list[self.idx] = self.missing
544        else:
545            list[self.idx] = (int(value)-self.disp)*self.mult
546        return
547
548    def descript(self):
549        if self.disp==0 and self.mult==1:
550            return ['%s'%(self.attr.name)]
551        else:
552            return ['%s(x)=(x-%f)*%f'%(self.attr.name,self.disp,self.mult)]
553
554    def description(self):
555        return (1,'%s'%(self.attr.name),['%s'%(v) for v in self.attr.values])
556
557    def inverse(self,list):
558        return self.attr(int((list[self.idx]/self.mult)+self.disp+0.5))
559
560class Binarizer:
561    def __init__(self,idx,attr,isclass=0):
562        self.idx = idx
563        self.nidx = idx+len(attr.values)
564        self.attr = attr
565        self.isclass = isclass
566        return
567
568    def status(self):
569        print "binarizer: "
570        print "\tattr:",self.attr
571        print "\tidx: ",self.idx
572        print "\tidxn:",self.nidx-self.idx
573           
574    def learn(self,value):
575        return
576           
577    def prepareSVM(self,nomo):
578        if self.isclass or nomo:
579            self.min = SVM_CMIN
580        else:
581            self.min = SVM_MIN
582        self.max = SVM_MAX
583        self.missing = (0,1)
584        return
585
586    def prepareLR(self):
587        self.min = 0.0
588        self.max = 1.0
589        self.missing = 1.0/(self.nidx-self.idx)
590        return
591
592    def activate(self):
593        pass
594   
595    def apply(self,ex,list):
596        (value,spec) = _getattr(ex,self.attr)
597        if spec:
598            for i in xrange(self.idx,self.nidx):
599                list[i] = self.missing
600        else:
601            for i in xrange(self.idx,self.nidx):
602                list[i] = self.min
603            list[self.idx+int(value)] = self.max
604
605    def descript(self):
606        return ['%s=%s'%(self.attr.name,v) for v in self.attr.values]
607
608    def description(self):
609        return (1,'%s'%(self.attr.name),['%s'%(v) for v in self.attr.values])
610
611    def inverse(self,list):
612        best = -1
613        bestv = 1e200
614        for i in xrange(self.idx,self.nidx):
615            val = abs(list[self.idx]-self.max)
616            if val < bestv:
617                bestv = val
618                best = i
619        assert(best>=0)
620        return self.attr(best-self.idx)
621
622class Dummy:
623    def __init__(self,idx,attr,isclass=0):
624        self.idx = idx
625        self.nidx = idx+len(attr.values)-1
626        self.counts = [0]*len(attr.values)
627        self.attr = attr
628        self.isclass = 0
629        return
630
631    def learn(self,value):
632        if not value.isSpecial():
633            self.counts[int(value)] += 1
634        return
635
636    def activate(self):
637        # identify the most frequent one
638        i = 0
639        maxx = max(self.counts)
640        self.maxi = -1
641        self.lut = [-1]*len(self.counts)
642        for x in xrange(len(self.counts)):
643            if self.counts[x]==maxx and self.maxi < 0:
644                self.maxi = x
645            else:
646                self.lut[x] = i
647                i += 1
648       
649               
650    def status(self):
651        print "dummy: "
652        print "\tattr:",self.attr
653        print "\tidx: ",self.idx
654        print "\tidxn:",self.nidx-self.idx
655           
656    def prepareSVM(self,nomo):
657        if self.isclass or nomo:
658            self.min = SVM_CMIN
659        else:
660            self.min = SVM_MIN
661        self.max = SVM_MAX
662        self.missing = (0,1)
663        return
664
665    def prepareLR(self):
666        self.min = 0.0
667        self.max = 1.0
668        self.missing = 0.0
669        return
670   
671    def apply(self,ex,list):
672        (value,spec) = _getattr(ex,self.attr)
673        if spec:
674            # missing value handling
675            for i in xrange(self.idx,self.nidx):
676                list[self.idx] = self.missing
677        else:
678            for i in xrange(self.idx,self.nidx):
679                list[self.idx] = self.min
680            i = self.lut[int(value)]
681            if i != -1:
682                list[self.idx+i] = self.max
683
684    def descript(self):
685        d = []
686        for x in xrange(len(self.attr.values)):
687            if self.lut[x] != -1:
688                d.append('%s=%s'%(self.attr.name,self.attr.values[x]))
689        return d
690
691    def description(self):
692        d = []
693        for x in xrange(len(self.attr.values)):
694            if self.lut[x] != -1:
695                d.append('%s'%(self.attr.values[x]))
696            else:
697                d.append('')
698        return (1,'%s'%(self.attr.name),d)
699
700    def inverse(self,list):
701        best = self.nidx
702        bestv = 1e200
703        for i in xrange(self.idx,self.nidx):
704            val = abs(list[self.idx]-self.max)
705            if val < bestv:
706                bestv = val
707                best = i
708        return self.attr(best-self.idx)
709
710class DomainTranslation:
711    def __init__(self, mode = 0, float_mode = 1):
712        # MODE:
713        # 0: always dummy
714        # 1: always binarize
715        # 2: binarize if more values than 2, else dummy
716        # FLOAT_MODE:
717        # 0 : normalize
718        # 1 : standardize
719        # 2 : quadratize
720        # 3 : cubize
721        # negative : Gaussianize
722        self.mode = mode
723        self.floatmode = float_mode
724
725    def analyse(self,examples,weight=0,warning=0):
726        # attributes
727        self.trans = []
728        self.weight = weight
729        idx = 0
730        for i in examples.domain.attributes:
731            if i.varType == 2:
732                # continuous
733                if self.floatmode == 0:
734                    t = Scalizer(idx,i)
735                elif self.floatmode == 1:
736                    t = Standardizer(idx,i)
737                elif self.floatmode == 2:
738                    t = Quadratizer(idx,i)
739                elif self.floatmode == 3:
740                    t = Cubizer(idx,i)
741                elif self.floatmode < 0:
742                    t = Gaussianizer(idx,i,isclass=0,n_bells=-self.floatmode)
743            else:
744                if i.varType == 1:
745                   
746                    if self.mode == 0:
747                        t = Dummy(idx,i)
748                    elif self.mode == 1:
749                        t = Binarizer(idx,i)
750                    elif self.mode == 2:
751                        if len(i.values) > 2:
752                            t = Binarizer(idx,i)
753                        else:
754                            t = Dummy(idx,i)
755            self.trans.append(t)
756            idx = t.nidx
757
758        # class: in its own array
759        i = examples.domain.classVar
760        if i.varType == 2:
761            # continuous
762            self.cv = Scalizer(0,i,isclass=1)
763        else:
764            if i.varType == 1:
765                # discrete
766                if len(i.values) > 2 and warning:
767                    warnings.warn("Simulating classification with regression. It's better to use orngMultiClass!")
768                self.cv = Ordinalizer(0,i,isclass=1)
769
770        # learning the properties of transformers
771        for j in examples:
772            for i in xrange(len(self.trans)):
773                self.trans[i].learn(j[i])
774            self.cv.learn(j.getclass())
775
776        # do the final preparations
777        for i in xrange(len(self.trans)):
778            self.trans[i].activate()
779
780    def prepareLR(self):
781        # preparation
782        self.cv.prepareLR()
783        self.missing = 1
784        self.weights = 1
785        for i in self.trans:
786            i.prepareLR()
787
788    def prepareSVM(self,nomo):
789        # preparation
790        self.cv.prepareSVM(nomo)
791        self.missing = 0
792        self.weights = 0 # SVM is not compatible with example weighting
793        for i in self.trans:
794            i.prepareSVM(nomo)
795
796    def transformClass(self, classvector):
797        # used for getting the label list
798        r = []
799        for i in classvector:
800            newc = [0.0]
801            x = orange.Example(orange.Domain([self.cv.attr]),[i])
802            self.cv.apply(x,newc)
803            r.append(newc[0])
804        return r
805   
806    def transform(self,examples):
807        # transformation of examples
808        newt = []
809        for j in examples:
810            newv = [0.0]*self.trans[-1].nidx
811            newc = [0.0]
812            for i in xrange(len(self.trans)):
813                self.trans[i].apply(j,newv)
814            self.cv.apply(j,newc)
815
816            # process weight
817            if self.weight != 0 and self.weights != 0:
818                newc = [(newc[0],j.getmeta(self.weight))]
819
820            newt.append(newv + newc)
821        return newt
822
823    def extransform(self,example):
824            newv = [0.0]*self.trans[-1].nidx
825            for i in xrange(len(self.trans)):
826                self.trans[i].apply(example,newv)
827            return newv
828
829    def getClass(self,label):
830        # inverse transformation
831        return self.cv.inverse([label])
832   
833    def status(self):
834        print "Attributes:"
835        for i in self.trans:
836            print i.status()
837        print "Class:"
838        print self.cv.status()
839        print "dummy: "           
840
841    def description(self):
842        ds = []
843        for i in xrange(len(self.trans)):
844            ds += self.trans[i].description()
845        return (ds,self.cv.description())
Note: See TracBrowser for help on using the repository browser.