Документ взят из кэша поисковой машины. Адрес оригинального документа : http://kodomo.fbb.msu.ru/hg/allpy/annotate/d60628e29b24/allpy/base.py
Дата изменения: Unknown
Дата индексирования: Sun Mar 2 07:05:03 2014
Кодировка:
allpy: allpy/base.py annotate

allpy

annotate allpy/base.py @ 261:d60628e29b24

Moved contents of block.py to base.py without any changes.
author Daniil Alexeyevsky <me.dendik@gmail.com>
date Tue, 14 Dec 2010 21:02:49 +0300
parents aae821828b03
children e361a7b7d9aa
rev   line source
me@261 1 import sys
me@261 2 import os
me@261 3 from tempfile import NamedTemporaryFile
me@261 4
me@261 5 import config
me@261 6 from graph import Graph
me@261 7 from Bio.PDB import Superimposer
bnagaev@249 8 from fasta import save_fasta
me@260 9 import data.codes
me@260 10
me@260 11 class MonomerType(object):
me@260 12 """Class of monomer types.
me@260 13
me@260 14 Each MonomerType object represents a known monomer type, e.g. Valine,
me@260 15 and is referenced to by each instance of monomer in a given sequence.
me@260 16
me@260 17 - `name`: full name of monomer type
me@260 18 - `code1`: one-letter code
me@260 19 - `code3`: three-letter code
me@260 20 - `is_modified`: either of True or False
me@260 21
me@260 22 class atributes:
me@260 23
me@260 24 - `by_code1`: a mapping from one-letter code to MonomerType object
me@260 25 - `by_code3`: a mapping from three-letter code to MonomerType object
me@260 26 - `by_name`: a mapping from monomer name to MonomerType object
me@260 27 - `instance_type`: class of Monomer objects to use when creating new
me@260 28 objects; this must be redefined in descendent classes
me@260 29
me@260 30 All of the class attributes MUST be redefined when subclassing.
me@260 31 """
me@260 32
me@260 33 by_code1 = {}
me@260 34 by_code3 = {}
me@260 35 by_name = {}
me@260 36 instance_type = None
me@260 37
me@260 38 def __init__(self, name="", code1="", code3="", is_modified=False):
me@260 39 self.name = name.capitalize()
me@260 40 self.code1 = code1.upper()
me@260 41 self.code3 = code3.upper()
me@260 42 self.is_modified = bool(is_modified)
me@260 43 if not is_modified:
me@260 44 self.by_code1[self.code1] = self
me@260 45 self.by_code3[code3] = self
me@260 46 self.by_name[name] = self
me@260 47 # We duplicate distinguished long names into MonomerType itself,
me@260 48 # so that we can use MonomerType.from_code3 to create the relevant
me@260 49 # type of monomer.
me@260 50 MonomerType.by_code3[code3] = self
me@260 51 MonomerType.by_name[name] = self
me@260 52
me@260 53 @classmethod
me@260 54 def _initialize(cls, type_letter, codes=data.codes.codes):
me@260 55 """Create all relevant instances of MonomerType.
me@260 56
me@260 57 `type_letter` is either of:
me@260 58
me@260 59 - 'p' for protein
me@260 60 - 'd' for DNA
me@260 61 - 'r' for RNA
me@260 62
me@260 63 `codes` is a table of monomer codes
me@260 64 """
me@260 65 for type, code1, is_modified, code3, name in codes:
me@260 66 if type == type_letter:
me@260 67 cls(name, code1, code3, is_modified)
me@260 68
me@260 69 @classmethod
me@260 70 def from_code1(cls, code1):
me@260 71 """Return monomer type by one-letter code."""
me@260 72 return cls.by_code1[code1.upper()]
me@260 73
me@260 74 @classmethod
me@260 75 def from_code3(cls, code3):
me@260 76 """Return monomer type by three-letter code."""
me@260 77 return cls.by_code3[code3.upper()]
me@260 78
me@260 79 @classmethod
me@260 80 def from_name(cls, name):
me@260 81 """Return monomer type by name."""
me@260 82 return cls.by_name[name.capitalize()]
me@260 83
me@260 84 def instance(self):
me@260 85 """Create a new monomer of given type."""
me@260 86 return self.instance_type(self)
me@260 87
me@260 88 def __eq__(self, other):
me@260 89 if hasattr(other, "type"):
me@260 90 return self is other.type
me@260 91 return self is other
me@260 92
me@260 93 class Monomer(object):
me@260 94 """Monomer object.
me@260 95
me@260 96 attributes:
me@260 97
me@260 98 - `type`: type of monomer (a MonomerType object)
me@260 99
me@260 100 class attribute `monomer_type` is MonomerType or either of it's subclasses,
me@260 101 it is used when creating new monomers. It MUST be redefined when subclassing Monomer.
me@260 102 """
me@260 103 monomer_type = MonomerType
me@260 104
me@260 105 def __init__(self, type):
me@260 106 self.type = type
me@260 107
me@260 108 @classmethod
me@260 109 def from_code1(cls, code1):
me@260 110 return cls(cls.monomer_type.by_code1[code1.upper()])
me@260 111
me@260 112 @classmethod
me@260 113 def from_code3(cls, code3):
me@260 114 return cls(cls.monomer_type.by_code3[code3.upper()])
me@260 115
me@260 116 @classmethod
me@260 117 def from_name(cls, name):
me@260 118 return cls(cls.monomer_type.by_name[name.capitalize()])
me@260 119
me@260 120 def __eq__(self, other):
me@260 121 if hasattr(other, "type"):
me@260 122 return self.type is other.type
me@260 123 return self.type is other
bnagaev@239 124
bnagaev@239 125 class Sequence(list):
bnagaev@243 126 """ Sequence of Monomers
bnagaev@243 127
bnagaev@243 128 list of monomer objects
bnagaev@243 129
bnagaev@243 130 Mandatory data:
bnagaev@243 131 * name -- str with the name of sequence
bnagaev@243 132 * description -- str with description of the sequence
bnagaev@243 133 """
bnagaev@243 134 pass
bnagaev@243 135
bnagaev@249 136 class Alignment(dict):
bnagaev@249 137 """ Alignment
bnagaev@249 138
bnagaev@249 139 {<Sequence object>:[<Monomer object>,None,<Monomer object>]}
bnagaev@249 140 keys are the Sequence objects, values are the lists, which
bnagaev@249 141 contain monomers of those sequences or None for gaps in the
bnagaev@249 142 corresponding sequence of alignment
bnagaev@249 143 """
bnagaev@249 144 # _sequences -- list of Sequence objects. Sequences don't contain gaps
bnagaev@249 145 # - see sequence.py module
bnagaev@249 146
bnagaev@249 147 def __init__(self, *args):
bnagaev@249 148 """overloaded constructor
bnagaev@249 149
bnagaev@249 150 Alignment() -> new empty Alignment
bnagaev@249 151 Alignment(sequences, body) -> new Alignment with sequences and
bnagaev@249 152 body initialized from arguments
bnagaev@249 153 Alignment(fasta_file) -> new Alignment, read body and sequences
bnagaev@249 154 from fasta file
bnagaev@249 155
bnagaev@249 156 """
bnagaev@249 157 if len(args)>1:#overloaded constructor
bnagaev@249 158 self.sequences=args[0]
bnagaev@249 159 self.body=args[1]
bnagaev@249 160 elif len(args)==0:
bnagaev@249 161 self.sequences=[]
bnagaev@249 162 self.body={}
bnagaev@249 163 else:
bnagaev@249 164 self.sequences, self.body = Alignment.from_fasta(args[0])
bnagaev@249 165
bnagaev@249 166 def length(self):
bnagaev@249 167 """ Returns width, ie length of each sequence with gaps """
bnagaev@249 168 return max([len(line) for line in self.body.values()])
bnagaev@249 169
bnagaev@249 170 def height(self):
bnagaev@249 171 """ The number of sequences in alignment (it's thickness). """
bnagaev@249 172 return len(self.body)
bnagaev@249 173
bnagaev@249 174 def identity(self):
bnagaev@249 175 """ Calculate the identity of alignment positions for colouring.
bnagaev@249 176
bnagaev@249 177 For every (row, column) in alignment the percentage of the exactly
bnagaev@249 178 same residue in the same column in the alignment is calculated.
bnagaev@249 179 The data structure is just like the Alignment.body, but istead of
bnagaev@249 180 monomers it contains float percentages.
bnagaev@249 181 """
bnagaev@249 182 # Oh, God, that's awful! Absolutely not understandable.
bnagaev@249 183 # First, calculate percentages of amino acids in every column
bnagaev@249 184 contribution = 1.0 / len(self.sequences)
bnagaev@249 185 all_columns = []
bnagaev@249 186 for position in range(len(self)):
bnagaev@249 187 column_percentage = {}
bnagaev@249 188 for seq in self.body:
bnagaev@249 189 if self.body[seq][position] is not None:
bnagaev@249 190 aa = self.body[seq][position].code
bnagaev@249 191 else:
bnagaev@249 192 aa = None
bnagaev@249 193 if aa in allpy.data.amino_acids:
bnagaev@249 194 if aa in column_percentage.keys():
bnagaev@249 195 column_percentage[aa] += contribution
bnagaev@249 196 else:
bnagaev@249 197 column_percentage[aa] = contribution
bnagaev@249 198 all_columns.append(column_percentage)
bnagaev@249 199 # Second, map these percentages onto the alignment
bnagaev@249 200 self.identity_percentages = {}
bnagaev@249 201 for seq in self.sequences:
bnagaev@249 202 self.identity_percentages[seq] = []
bnagaev@249 203 for seq in self.identity_percentages:
bnagaev@249 204 line = self.identity_percentages[seq]
bnagaev@249 205 for position in range(len(self)):
bnagaev@249 206 if self.body[seq][position] is not None:
bnagaev@249 207 aa = self.body[seq][position].code
bnagaev@249 208 else:
bnagaev@249 209 aa = None
bnagaev@249 210 line.append(all_columns[position].get(aa))
bnagaev@249 211 return self.identity_percentages
bnagaev@249 212
bnagaev@249 213 @staticmethod
bnagaev@249 214 def from_fasta(file, monomer_kind=AminoAcidType):
bnagaev@249 215 """ Import data from fasta file
bnagaev@249 216
bnagaev@249 217 monomer_kind is class, inherited from MonomerType
bnagaev@249 218
bnagaev@249 219 >>> import alignment
bnagaev@249 220 >>> sequences,body=alignment.Alignment.from_fasta(open("test.fasta"))
bnagaev@249 221 """
bnagaev@249 222 import re
bnagaev@249 223
bnagaev@249 224 sequences = []
bnagaev@249 225 body = {}
bnagaev@249 226
bnagaev@249 227 raw_sequences = file.read().split(">")
bnagaev@249 228 if len(raw_sequences) <= 1:
bnagaev@249 229 raise Exception("Wrong format of fasta-file %s" % file.name)
bnagaev@249 230
bnagaev@249 231 raw_sequences = raw_sequences[1:] #ignore everything before the first >
bnagaev@249 232 for raw in raw_sequences:
bnagaev@249 233 parsed_raw_sequence = raw.split("\n")
bnagaev@249 234 parsed_raw_sequence = [s.strip() for s in parsed_raw_sequence]
bnagaev@249 235 name_and_description = parsed_raw_sequence[0]
bnagaev@249 236 name_and_description = name_and_description.split(" ",1)
bnagaev@249 237 if len(name_and_description) == 2:
bnagaev@249 238 name, description = name_and_description
bnagaev@249 239 elif len(name_and_description) == 1:
bnagaev@249 240 #if there is description
bnagaev@249 241 name = name_and_description[0]
bnagaev@249 242 description = ''
bnagaev@249 243 else:
bnagaev@249 244 raise Exception("Wrong name of sequence %(name)$ fasta-file %(file)s" % \
bnagaev@249 245 {'name': name, 'file': file.name})
bnagaev@249 246
bnagaev@249 247 if len(parsed_raw_sequence) <= 1:
bnagaev@249 248 raise Exception("Wrong format of sequence %(name)$ fasta-file %(file)s" % \
bnagaev@249 249 {'name': name, 'file': file.name})
bnagaev@249 250 string = ""
bnagaev@249 251 for piece in parsed_raw_sequence[1:]:
bnagaev@249 252 piece_without_whitespace_chars = re.sub("\s", "", piece)
bnagaev@249 253 string += piece_without_whitespace_chars
bnagaev@249 254 monomers = [] #convert into Monomer objects
bnagaev@249 255 body_list = [] #create the respective list in body dict
bnagaev@249 256 for current_monomer in string:
bnagaev@249 257 if current_monomer not in ["-", ".", "~"]:
bnagaev@249 258 monomers.append(monomer_kind.from_code1(current_monomer).instance())
bnagaev@249 259 body_list.append(monomers[-1])
bnagaev@249 260 else:
bnagaev@249 261 body_list.append(None)
bnagaev@249 262 s = sequence.Sequence(monomers, name, description)
bnagaev@249 263 sequences.append(s)
bnagaev@249 264 body[s] = body_list
bnagaev@249 265 return sequences, body
bnagaev@249 266
bnagaev@249 267 @staticmethod
bnagaev@249 268 def from_sequences(*sequences):
bnagaev@249 269 """ Constructs new alignment from sequences
bnagaev@249 270
bnagaev@249 271 Add None's to right end to make equal lengthes of alignment sequences
bnagaev@249 272 """
bnagaev@249 273 alignment = Alignment()
bnagaev@249 274 alignment.sequences = sequences
bnagaev@249 275 max_length = max(len(sequence) for sequence in sequences)
bnagaev@249 276 for sequence in sequences:
bnagaev@249 277 gaps_count = max_length - len(sequence)
bnagaev@249 278 alignment.body[sequence] = sequence.monomers + [None] * gaps_count
bnagaev@249 279 return alignment
bnagaev@249 280
bnagaev@249 281 def save_fasta(self, out_file, long_line=70, gap='-'):
bnagaev@249 282 """ Saves alignment to given file
bnagaev@249 283
bnagaev@249 284 Splits long lines to substrings of length=long_line
bnagaev@249 285 To prevent this, set long_line=None
bnagaev@249 286 """
bnagaev@249 287 block.Block(self).save_fasta(out_file, long_line=long_line, gap=gap)
bnagaev@249 288
bnagaev@249 289 def muscle_align(self):
bnagaev@249 290 """ Simple align ths alignment using sequences (muscle)
bnagaev@249 291
bnagaev@249 292 uses old Monomers and Sequences objects
bnagaev@249 293 """
bnagaev@249 294 tmp_file = NamedTemporaryFile(delete=False)
bnagaev@249 295 self.save_fasta(tmp_file)
bnagaev@249 296 tmp_file.close()
bnagaev@249 297 os.system("muscle -in %(tmp)s -out %(tmp)s" % {'tmp': tmp_file.name})
bnagaev@249 298 sequences, body = Alignment.from_fasta(open(tmp_file.name))
bnagaev@249 299 for sequence in self.sequences:
bnagaev@249 300 try:
bnagaev@249 301 new_sequence = [i for i in sequences if sequence==i][0]
bnagaev@249 302 except:
bnagaev@249 303 raise Exception("Align: Cann't find sequence %s in muscle output" % \
bnagaev@249 304 sequence.name)
bnagaev@249 305 old_monomers = iter(sequence.monomers)
bnagaev@249 306 self.body[sequence] = []
bnagaev@249 307 for monomer in body[new_sequence]:
bnagaev@249 308 if not monomer:
bnagaev@249 309 self.body[sequence].append(monomer)
bnagaev@249 310 else:
bnagaev@249 311 old_monomer = old_monomers.next()
bnagaev@249 312 if monomer != old_monomer:
bnagaev@249 313 raise Exception("Align: alignment errors")
bnagaev@249 314 self.body[sequence].append(old_monomer)
bnagaev@249 315 os.unlink(tmp_file.name)
bnagaev@249 316
bnagaev@249 317 def column(self, sequence=None, sequences=None, original=None):
bnagaev@249 318 """ returns list of columns of alignment
bnagaev@249 319
bnagaev@249 320 sequence or sequences:
bnagaev@249 321 if sequence is given, then column is (original_monomer, monomer)
bnagaev@249 322 if sequences is given, then column is (original_monomer, {sequence: monomer})
bnagaev@249 323 if both of them are given, it is an error
bnagaev@249 324 original (Sequence type):
bnagaev@249 325 if given, this filters only columns represented by original sequence
bnagaev@249 326 """
bnagaev@249 327 if sequence and sequences:
bnagaev@249 328 raise Exception("Wrong usage. read help")
bnagaev@249 329 indexes = dict([(v, k) for( k, v) in enumerate(self.sequences)])
bnagaev@249 330 alignment = self.body.items()
bnagaev@249 331 alignment.sort(key=lambda i: indexes[i[0]])
bnagaev@249 332 alignment = [monomers for seq, monomers in alignment]
bnagaev@249 333 for column in zip(*alignment):
bnagaev@249 334 if not original or column[indexes[original]]:
bnagaev@249 335 if sequence:
bnagaev@249 336 yield (column[indexes[original]], column[indexes[sequence]])
bnagaev@249 337 else:
bnagaev@249 338 yield (column[indexes[original]],
bnagaev@249 339 dict([(s, column[indexes[s]]) for s in sequences]))
bnagaev@249 340
bnagaev@249 341 def secstr(self, sequence, pdb_chain, gap='-'):
bnagaev@249 342 """ Returns string representing secondary structure """
bnagaev@249 343 return ''.join([
bnagaev@249 344 (sequence.pdb_secstr[pdb_chain][m] if sequence.secstr_has(pdb_chain, m) else gap)
bnagaev@249 345 for m in self.body[sequence]])
bnagaev@249 346
bnagaev@249 347 class Block(object):
me@261 348 """ Block of alignment
me@261 349
me@261 350 Mandatory data:
me@261 351 * self.alignment -- alignment object, which the block belongs to
me@261 352 * self.sequences - set of sequence objects that contain monomers
me@261 353 and/or gaps, that constitute the block
me@261 354 * self.positions -- list of positions of the alignment.body that
me@261 355 are included in the block; position[i+1] is always to the right from position[i]
me@261 356
me@261 357 Don't change self.sequences -- it may be a link to other block.sequences
me@261 358
me@261 359 How to create a new block:
me@261 360 >>> import alignment
me@261 361 >>> import block
me@261 362 >>> proj = alignment.Alignment(open("test.fasta"))
me@261 363 >>> block1 = block.Block(proj)
me@261 364 """
me@261 365
me@261 366 def __init__(self, alignment, sequences=None, positions=None):
me@261 367 """ Builds new block from alignment
me@261 368
me@261 369 if sequences==None, all sequences are used
me@261 370 if positions==None, all positions are used
me@261 371 """
me@261 372 if sequences == None:
me@261 373 sequences = set(alignment.sequences) # copy
me@261 374 if positions == None:
me@261 375 positions = range(len(alignment))
me@261 376 self.alignment = alignment
me@261 377 self.sequences = sequences
me@261 378 self.positions = positions
me@261 379
me@261 380 def save_fasta(self, out_file, long_line=70, gap='-'):
me@261 381 """ Saves alignment to given file in fasta-format
me@261 382
me@261 383 No changes in the names, descriptions or order of the sequences
me@261 384 are made.
me@261 385 """
me@261 386 for sequence in self.sequences:
me@261 387 alignment_monomers = self.alignment.body[sequence]
me@261 388 block_monomers = [alignment_monomers[i] for i in self.positions]
me@261 389 string = ''.join([m.type.code1 if m else '-' for m in block_monomers])
me@261 390 save_fasta(out_file, string, sequence.name, sequence.description, long_line)
me@261 391
me@261 392 def geometrical_cores(self, max_delta=config.delta,
me@261 393 timeout=config.timeout, minsize=config.minsize,
me@261 394 ac_new_atoms=config.ac_new_atoms,
me@261 395 ac_count=config.ac_count):
me@261 396 """ Returns length-sorted list of blocks, representing GCs
me@261 397
me@261 398 max_delta -- threshold of distance spreading
me@261 399 timeout -- Bron-Kerbosh timeout (then fast O(n ln n) algorithm)
me@261 400 minsize -- min size of each core
me@261 401 ac_new_atoms -- min part or new atoms in new alternative core
me@261 402 current GC is compared with each of already selected GCs
me@261 403 if difference is less then ac_new_atoms, current GC is skipped
me@261 404 difference = part of new atoms in current core
me@261 405 ac_count -- max number of cores (including main core)
me@261 406 -1 means infinity
me@261 407 If more than one pdb chain for some sequence provided, consider all of them
me@261 408 cost is calculated as 1 / (delta + 1)
me@261 409 delta in [0, +inf) => cost in (0, 1]
me@261 410 """
me@261 411 nodes = self.positions
me@261 412 lines = {}
me@261 413 for i in self.positions:
me@261 414 for j in self.positions:
me@261 415 if i < j:
me@261 416 distances = []
me@261 417 for sequence in self.sequences:
me@261 418 for chain in sequence.pdb_chains:
me@261 419 m1 = self.alignment.body[sequence][i]
me@261 420 m2 = self.alignment.body[sequence][j]
me@261 421 if m1 and m2:
me@261 422 r1 = sequence.pdb_residues[chain][m1]
me@261 423 r2 = sequence.pdb_residues[chain][m2]
me@261 424 ca1 = r1['CA']
me@261 425 ca2 = r2['CA']
me@261 426 d = ca1 - ca2 # Bio.PDB feature
me@261 427 distances.append(d)
me@261 428 if len(distances) >= 2:
me@261 429 delta = max(distances) - min(distances)
me@261 430 if delta <= max_delta:
me@261 431 lines[Graph.line(i, j)] = 1.0 / (1.0 + max_delta)
me@261 432 graph = Graph(nodes, lines)
me@261 433 cliques = graph.cliques(timeout=timeout, minsize=minsize)
me@261 434 GCs = []
me@261 435 for clique in cliques:
me@261 436 for GC in GCs:
me@261 437 if len(clique - set(GC.positions)) < ac_new_atoms * len(clique):
me@261 438 break
me@261 439 else:
me@261 440 GCs.append(Block(self.alignment, self.sequences, clique))
me@261 441 if ac_count != -1 and len(GCs) >= ac_count:
me@261 442 break
me@261 443 return GCs
me@261 444
me@261 445 def xstring(self, x='X', gap='-'):
me@261 446 """ Returns string consisting of gap chars and chars x at self.positions
me@261 447
me@261 448 Length of returning string = length of alignment
me@261 449 """
me@261 450 monomers = [False] * len(self.alignment)
me@261 451 for i in self.positions:
me@261 452 monomers[i] = True
me@261 453 return ''.join([x if m else gap for m in monomers])
me@261 454
me@261 455 def save_xstring(self, out_file, name, description='', x='X', gap='-', long_line=70):
me@261 456 """ Save xstring and name in fasta format """
me@261 457 save_fasta(out_file, self.xstring(x=x, gap=gap), name, description, long_line)
me@261 458
me@261 459 def monomers(self, sequence):
me@261 460 """ Iterates monomers of this sequence from this block """
me@261 461 alignment_sequence = self.alignment.body[sequence]
me@261 462 return (alignment_sequence[i] for i in self.positions)
me@261 463
me@261 464 def ca_atoms(self, sequence, pdb_chain):
me@261 465 """ Iterates Ca-atom of monomers of this sequence from this block """
me@261 466 return (sequence.pdb_residues[pdb_chain][monomer] for monomer in self.monomers())
me@261 467
me@261 468 def sequences_chains(self):
me@261 469 """ Iterates pairs (sequence, chain) """
me@261 470 for sequence in self.alignment.sequences:
me@261 471 if sequence in self.sequences:
me@261 472 for chain in sequence.pdb_chains:
me@261 473 yield (sequence, chain)
me@261 474
me@261 475 def superimpose(self):
me@261 476 """ Superimpose all pdb_chains in this block """
me@261 477 sequences_chains = list(self.sequences_chains())
me@261 478 if len(sequences_chains) >= 1:
me@261 479 sup = Superimposer()
me@261 480 fixed_sequence, fixed_chain = sequences_chains.pop()
me@261 481 fixed_atoms = self.ca_atoms(fixed_sequence, fixed_chain)
me@261 482 for sequence, chain in sequences_chains:
me@261 483 moving_atoms = self.ca_atoms(sequence, chain)
me@261 484 sup.set_atoms(fixed_atoms, moving_atoms)
me@261 485 # Apply rotation/translation to the moving atoms
me@261 486 sup.apply(moving_atoms)
me@261 487
me@261 488 def pdb_save(self, out_file):
me@261 489 """ Save all sequences
me@261 490
me@261 491 Returns {(sequence, chain): CHAIN}
me@261 492 CHAIN is chain letter in new file
me@261 493 """
me@261 494 tmp_file = NamedTemporaryFile(delete=False)
me@261 495 tmp_file.close()
me@261 496
me@261 497 for sequence, chain in self.sequences_chains():
me@261 498 sequence.pdb_save(tmp_file.name, chain)
me@261 499 # TODO: read from tmp_file.name
me@261 500 # change CHAIN
me@261 501 # add to out_file
me@261 502
me@261 503 os.unlink(NamedTemporaryFile)
bnagaev@239 504
me@260 505 # vim: set ts=4 sts=4 sw=4 et: