annotate commons/core/seq/AlignedBioseqDB.py @ 58:5f5c9b74c2dd

Uploaded
author m-zytnicki
date Fri, 07 Feb 2014 11:53:36 -0500
parents 44d5973c188c
children
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
36
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
1 # Copyright INRA (Institut National de la Recherche Agronomique)
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
2 # http://www.inra.fr
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
3 # http://urgi.versailles.inra.fr
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
4 #
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
5 # This software is governed by the CeCILL license under French law and
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
6 # abiding by the rules of distribution of free software. You can use,
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
7 # modify and/ or redistribute the software under the terms of the CeCILL
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
8 # license as circulated by CEA, CNRS and INRIA at the following URL
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
9 # "http://www.cecill.info".
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
10 #
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
11 # As a counterpart to the access to the source code and rights to copy,
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
12 # modify and redistribute granted by the license, users are provided only
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
13 # with a limited warranty and the software's author, the holder of the
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
14 # economic rights, and the successive licensors have only limited
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
15 # liability.
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
16 #
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
17 # In this respect, the user's attention is drawn to the risks associated
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
18 # with loading, using, modifying and/or developing or reproducing the
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
19 # software by the user in light of its specific status of free software,
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
20 # that may mean that it is complicated to manipulate, and that also
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
21 # therefore means that it is reserved for developers and experienced
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
22 # professionals having in-depth computer knowledge. Users are therefore
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
23 # encouraged to load and test the software's suitability as regards their
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
24 # requirements in conditions enabling the security of their systems and/or
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
25 # data to be ensured and, more generally, to use and operate it in the
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
26 # same conditions as regards security.
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
27 #
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
28 # The fact that you are presently reading this means that you have had
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
29 # knowledge of the CeCILL license and that you accept its terms.
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
30
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
31
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
32 import sys
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
33 from commons.core.seq.BioseqDB import BioseqDB
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
34 from commons.core.seq.Bioseq import Bioseq
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
35 from commons.core.coord.Align import Align
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
36 from commons.core.coord.Range import Range
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
37 from commons.core.stat.Stat import Stat
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
38 from math import log
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
39
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
40
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
41 ## Multiple Sequence Alignment Representation
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
42 #
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
43 #
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
44 class AlignedBioseqDB( BioseqDB ):
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
45
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
46 def __init__( self, name="" ):
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
47 BioseqDB.__init__( self, name )
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
48 seqLength = self.getLength()
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
49 if self.getSize() > 1:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
50 for bs in self.db[1:]:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
51 if bs.getLength() != seqLength:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
52 print "ERROR: aligned sequences have different length"
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
53
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
54
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
55 ## Get length of the alignment
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
56 #
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
57 # @return length
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
58 # @warning name before migration was 'length'
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
59 #
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
60 def getLength( self ):
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
61 length = 0
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
62 if self.db != []:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
63 length = self.db[0].getLength()
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
64 return length
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
65
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
66
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
67 ## Get the true length of a given sequence (without gaps)
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
68 #
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
69 # @param header string header of the sequence to analyze
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
70 # @return length integer
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
71 # @warning name before migration was 'true_length'
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
72 #
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
73 def getSeqLengthWithoutGaps( self, header ):
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
74 bs = self.fetch( header )
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
75 count = 0
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
76 for pos in xrange(0,len(bs.sequence)):
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
77 if bs.sequence[pos] != "-":
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
78 count += 1
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
79 return count
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
80
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
81 def cleanMSA( self ):
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
82 #TODO: Refactoring
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
83 """clean the MSA"""
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
84 i2del = []
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
85
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
86 # for each sequence in the MSA
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
87 for seqi in xrange(0,self.getSize()):
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
88 if seqi in i2del:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
89 continue
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
90 #define it as the reference
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
91 ref = self.db[seqi].sequence
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
92 refHeader = self.db[seqi].header
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
93 # for each following sequence
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
94 for seq_next in xrange(seqi+1,self.getSize()):
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
95 if seq_next in i2del:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
96 continue
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
97 keep = 0
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
98 # for each position along the MSA
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
99 for posx in xrange(0,self.getLength()):
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
100 seq = self.db[seq_next].sequence
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
101 if seq[posx] != '-' and ref[posx] != '-':
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
102 keep = 1
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
103 break
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
104 seqHeader = self.db[seq_next].header
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
105 # if there is at least one gap between the ref seq and the other seq
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
106 # keep track of the shortest by recording it in "i2del"
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
107 if keep == 0:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
108
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
109 if self.getSeqLengthWithoutGaps(refHeader) < self.getSeqLengthWithoutGaps(seqHeader):
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
110 if seqi not in i2del:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
111 i2del.append( seqi )
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
112 else:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
113 if seq_next not in i2del:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
114 i2del.append( seq_next )
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
115
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
116 # delete from the MSA each seq present in the list "i2del"
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
117 for i in reversed(sorted(set(i2del))):
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
118 del self.db[i]
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
119
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
120 self.idx = {}
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
121 count = 0
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
122 for i in self.db:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
123 self.idx[i.header] = count
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
124 count += 1
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
125
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
126 ## Record the occurrences of symbols (A, T, G, C, N, -, ...) at each site
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
127 #
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
128 # @return: list of dico whose keys are symbols and values are their occurrences
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
129 #
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
130 def getListOccPerSite( self ):
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
131 lOccPerSite = [] # list of dictionaries, one per position on the sequence
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
132 n = 0 # nb of sequences parsed from the input file
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
133 firstSeq = True
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
134
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
135 # for each sequence in the bank
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
136 for bs in self.db:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
137 if bs.sequence == None:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
138 break
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
139 n += 1
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
140
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
141 # if it is the first to be parsed, create a dico at each site
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
142 if firstSeq:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
143 for i in xrange(0,len(bs.sequence)):
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
144 lOccPerSite.append( {} )
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
145 firstSeq = False
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
146
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
147 # for each site, add its nucleotide
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
148 for i in xrange(0,len(bs.sequence)):
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
149 nuc = bs.sequence[i].upper()
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
150 if lOccPerSite[i].has_key( nuc ):
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
151 lOccPerSite[i][nuc] += 1
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
152 else:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
153 lOccPerSite[i][nuc] = 1
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
154
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
155 return lOccPerSite
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
156
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
157 #TODO: review minNbNt !!! It should be at least 2 nucleotides to build a consensus...
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
158 ## Make a consensus from the MSA
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
159 #
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
160 # @param minNbNt: minimum nb of nucleotides to edit a consensus
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
161 # @param minPropNt: minimum proportion for the major nucleotide to be used, otherwise add 'N' (default=0.0)
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
162 # @param verbose: level of information sent to stdout (default=0/1)
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
163 # @return: consensus
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
164 #
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
165 def getConsensus( self, minNbNt, minPropNt=0.0, verbose=0 , isHeaderSAtannot=False):
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
166
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
167 maxPropN = 0.40 # discard consensus if more than 40% of N's
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
168
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
169 nbInSeq = self.getSize()
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
170 if verbose > 0:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
171 print "nb of aligned sequences: %i" % ( nbInSeq ); sys.stdout.flush()
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
172 if nbInSeq < 2:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
173 print "ERROR: can't make a consensus with less than 2 sequences"
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
174 sys.exit(1)
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
175 if minNbNt >= nbInSeq:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
176 minNbNt = nbInSeq - 1
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
177 print "minNbNt=%i" % ( minNbNt )
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
178 if minPropNt >= 1.0:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
179 print "ERROR: minPropNt=%.2f should be a proportion (below 1.0)" % ( minPropNt )
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
180 sys.exit(1)
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
181
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
182 lOccPerSite = self.getListOccPerSite()
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
183 nbSites = len(lOccPerSite)
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
184 if verbose > 0:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
185 print "nb of sites: %i" % ( nbSites ); sys.stdout.flush()
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
186
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
187 seqConsensus = ""
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
188
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
189 # for each site (i.e. each column of the MSA)
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
190 nbRmvColumns = 0
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
191 countSites = 0
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
192 for dNt2Occ in lOccPerSite:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
193 countSites += 1
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
194 if verbose > 1:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
195 print "site %s / %i" % ( str(countSites).zfill( len(str(nbSites)) ),
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
196 nbSites )
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
197 sys.stdout.flush()
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
198 occMaxNt = 0 # occurrences of the predominant nucleotide at this site
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
199 lBestNt = []
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
200 nbNt = 0 # total nb of A, T, G and C (no gap)
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
201
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
202 # for each distinct symbol at this site (A, T, G, C, N, -,...)
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
203 for j in dNt2Occ.keys():
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
204 if j != "-":
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
205 nbNt += dNt2Occ[j]
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
206 if verbose > 1:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
207 print "%s: %i" % ( j, dNt2Occ[j] )
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
208 if dNt2Occ[j] > occMaxNt:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
209 occMaxNt = dNt2Occ[j]
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
210 lBestNt = [ j ]
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
211 elif dNt2Occ[j] == occMaxNt:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
212 lBestNt.append( j )
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
213 if nbNt == 0: # some MSA programs can remove some sequences (e.g. Muscle after Recon) or when using Refalign (non-alignable TE fragments put together via a refseq)
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
214 nbRmvColumns += 1
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
215
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
216 if len( lBestNt ) >= 1:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
217 bestNt = lBestNt[0]
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
218
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
219 # if the predominant nucleotide occurs in less than x% of the sequences, put a "N"
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
220 if minPropNt > 0.0 and nbNt != 0 and float(occMaxNt)/float(nbNt) < minPropNt:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
221 bestNt = "N"
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
222
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
223 if int(nbNt) >= int(minNbNt):
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
224 seqConsensus += bestNt
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
225 if verbose > 1:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
226 print "-> %s" % ( bestNt )
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
227
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
228 if nbRmvColumns:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
229 if nbRmvColumns == 1:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
230 print "WARNING: 1 site was removed (%.2f%%)" % (nbRmvColumns / float(nbSites) * 100)
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
231 else:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
232 print "WARNING: %i sites were removed (%.2f%%)" % ( nbRmvColumns, nbRmvColumns / float(nbSites) * 100 )
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
233 sys.stdout.flush()
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
234 if seqConsensus == "":
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
235 print "WARNING: no consensus can be built (no sequence left)"
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
236 return
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
237
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
238 propN = seqConsensus.count("N") / float(len(seqConsensus))
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
239 if propN >= maxPropN:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
240 print "WARNING: no consensus can be built (%i%% of N's >= %i%%)" % ( propN * 100, maxPropN * 100 )
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
241 return
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
242 elif propN >= maxPropN * 0.5:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
243 print "WARNING: %i%% of N's" % ( propN * 100 )
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
244
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
245 consensus = Bioseq()
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
246 consensus.sequence = seqConsensus
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
247 if isHeaderSAtannot:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
248 header = self.db[0].header
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
249 pyramid = header.split("Gr")[1].split("Cl")[0]
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
250 pile = header.split("Cl")[1].split(" ")[0]
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
251 consensus.header = "consensus=%s length=%i nbAlign=%i pile=%s pyramid=%s" % (self.name, len(seqConsensus), self.getSize(), pile, pyramid)
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
252 else:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
253 consensus.header = "consensus=%s length=%i nbAlign=%i" % ( self.name, len(seqConsensus), self.getSize() )
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
254
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
255 if verbose > 0:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
256
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
257 statEntropy = self.getEntropy( verbose - 1 )
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
258 print "entropy: %s" % ( statEntropy.stringQuantiles() )
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
259 sys.stdout.flush()
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
260
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
261 return consensus
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
262
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
263
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
264 ## Get the entropy of the whole multiple alignment (only for A, T, G and C)
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
265 #
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
266 # @param verbose level of verbosity
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
267 #
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
268 # @return statistics about the entropy of the MSA
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
269 #
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
270 def getEntropy( self, verbose=0 ):
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
271
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
272 stats = Stat()
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
273
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
274 # get the occurrences of symbols at each site
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
275 lOccPerSite = self.getListOccPerSite()
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
276
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
277 countSite = 0
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
278
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
279 # for each site
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
280 for dSymbol2Occ in lOccPerSite:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
281 countSite += 1
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
282
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
283 # count the number of nucleotides (A, T, G and C, doesn't count gap '-')
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
284 nbNt = 0
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
285 dATGC2Occ = {}
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
286 for base in ["A","T","G","C"]:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
287 dATGC2Occ[ base ] = 0.0
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
288 for nt in dSymbol2Occ.keys():
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
289 if nt != "-":
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
290 nbNt += dSymbol2Occ[ nt ]
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
291 checkedNt = self.getATGCNFromIUPAC( nt )
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
292 if checkedNt in ["A","T","G","C"] and dSymbol2Occ.has_key( checkedNt ):
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
293 dATGC2Occ[ checkedNt ] += 1 * dSymbol2Occ[ checkedNt ]
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
294 else: # for 'N'
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
295 if dSymbol2Occ.has_key( checkedNt ):
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
296 dATGC2Occ[ "A" ] += 0.25 * dSymbol2Occ[ checkedNt ]
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
297 dATGC2Occ[ "T" ] += 0.25 * dSymbol2Occ[ checkedNt ]
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
298 dATGC2Occ[ "G" ] += 0.25 * dSymbol2Occ[ checkedNt ]
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
299 dATGC2Occ[ "C" ] += 0.25 * dSymbol2Occ[ checkedNt ]
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
300 if verbose > 2:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
301 for base in dATGC2Occ.keys():
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
302 print "%s: %i" % ( base, dATGC2Occ[ base ] )
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
303
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
304 # compute the entropy for the site
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
305 entropySite = 0.0
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
306 for nt in dATGC2Occ.keys():
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
307 entropySite += self.computeEntropy( dATGC2Occ[ nt ], nbNt )
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
308 if verbose > 1:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
309 print "site %i (%i nt): entropy = %.3f" % ( countSite, nbNt, entropySite )
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
310 stats.add( entropySite )
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
311
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
312 return stats
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
313
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
314
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
315 ## Get A, T, G, C or N from an IUPAC letter
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
316 # IUPAC = ['A','T','G','C','U','R','Y','M','K','W','S','B','D','H','V','N']
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
317 #
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
318 # @return A, T, G, C or N
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
319 #
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
320 def getATGCNFromIUPAC( self, nt ):
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
321 iBs = Bioseq()
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
322 return iBs.getATGCNFromIUPAC( nt )
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
323
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
324
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
325 ## Compute the entropy based on the occurrences of a certain nucleotide and the total number of nucleotides
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
326 #
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
327 def computeEntropy( self, nbOcc, nbNt ):
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
328 if nbOcc == 0.0:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
329 return 0.0
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
330 else:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
331 freq = nbOcc / float(nbNt)
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
332 return - freq * log(freq) / log(2)
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
333
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
334
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
335 ## Save the multiple alignment as a matrix with '0' if gap, '1' otherwise
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
336 #
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
337 def saveAsBinaryMatrix( self, outFile ):
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
338 outFileHandler = open( outFile, "w" )
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
339 for bs in self.db:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
340 string = "%s" % ( bs.header )
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
341 for nt in bs.sequence:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
342 if nt != "-":
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
343 string += "\t%i" % ( 1 )
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
344 else:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
345 string += "\t%i" % ( 0 )
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
346 outFileHandler.write( "%s\n" % ( string ) )
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
347 outFileHandler.close()
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
348
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
349
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
350 ## Return a list of Align instances corresponding to the aligned regions (without gaps)
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
351 #
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
352 # @param query string header of the sequence considered as query
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
353 # @param subject string header of the sequence considered as subject
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
354 #
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
355 def getAlignList( self, query, subject ):
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
356 lAligns = []
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
357 alignQ = self.fetch( query ).sequence
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
358 alignS = self.fetch( subject ).sequence
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
359 createNewAlign = True
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
360 indexAlign = 0
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
361 indexQ = 0
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
362 indexS = 0
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
363 while indexAlign < len(alignQ):
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
364 if alignQ[ indexAlign ] != "-" and alignS[ indexAlign ] != "-":
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
365 indexQ += 1
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
366 indexS += 1
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
367 if createNewAlign:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
368 iAlign = Align( Range( query, indexQ, indexQ ),
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
369 Range( subject, indexS, indexS ),
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
370 0,
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
371 int( alignQ[ indexAlign ] == alignS[ indexAlign ] ),
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
372 int( alignQ[ indexAlign ] == alignS[ indexAlign ] ) )
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
373 lAligns.append( iAlign )
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
374 createNewAlign = False
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
375 else:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
376 lAligns[-1].range_query.end += 1
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
377 lAligns[-1].range_subject.end += 1
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
378 lAligns[-1].score += int( alignQ[ indexAlign ] == alignS[ indexAlign ] )
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
379 lAligns[-1].identity += int( alignQ[ indexAlign ] == alignS[ indexAlign ] )
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
380 else:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
381 if not createNewAlign:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
382 lAligns[-1].identity = 100 * lAligns[-1].identity / lAligns[-1].getLengthOnQuery()
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
383 createNewAlign = True
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
384 if alignQ[ indexAlign ] != "-":
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
385 indexQ += 1
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
386 elif alignS[ indexAlign ] != "-":
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
387 indexS += 1
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
388 indexAlign += 1
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
389 if not createNewAlign:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
390 lAligns[-1].identity = 100 * lAligns[-1].identity / lAligns[-1].getLengthOnQuery()
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
391 return lAligns
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
392
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
393
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
394 def removeGaps(self):
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
395 for iBs in self.db:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
396 iBs.removeSymbol( "-" )
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
397
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
398 ## Compute mean per cent identity for MSA.
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
399 # First sequence in MSA is considered as reference sequence.
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
400 #
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
401 #
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
402 def computeMeanPcentIdentity(self):
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
403 seqRef = self.db[0]
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
404 sumPcentIdentity = 0
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
405
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
406 for seq in self.db[1:]:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
407 pcentIdentity = self._computePcentIdentityBetweenSeqRefAndCurrentSeq(seqRef, seq)
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
408 sumPcentIdentity = sumPcentIdentity + pcentIdentity
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
409
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
410 nbSeq = len(self.db[1:])
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
411 meanPcentIdentity = round (sumPcentIdentity/nbSeq)
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
412
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
413 return meanPcentIdentity
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
414
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
415 def _computePcentIdentityBetweenSeqRefAndCurrentSeq(self, seqRef, seq):
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
416 indexOnSeqRef = 0
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
417 sumIdentity = 0
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
418 for nuclSeq in seq.sequence:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
419 nuclRef = seqRef.sequence[indexOnSeqRef]
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
420
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
421 if nuclRef != "-" and nuclRef == nuclSeq:
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
422 sumIdentity = sumIdentity + 1
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
423 indexOnSeqRef = indexOnSeqRef + 1
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
424
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
425 return float(sumIdentity) / float(seqRef.getLength()) * 100
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
426
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
427
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
428
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
429
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
430
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
431
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
432
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
433
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
434
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
435
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
436
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
437
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
438
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
439
44d5973c188c Uploaded
m-zytnicki
parents:
diff changeset
440