-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathptdomain.py
More file actions
855 lines (743 loc) · 34.4 KB
/
Copy pathptdomain.py
File metadata and controls
855 lines (743 loc) · 34.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
###############################################################################
#
# ptdomain.py - object to represent protein domains and functions to
# parse into domains from external programs
#
# File: ptdomain.py
# Author: Alex Stivala
# Created: September 2007
#
# $Id: ptdomain.py 2011 2008-10-30 01:54:20Z astivala $
#
# PTDomain is a class representing a protein domain. A domain is represented
# by a list of segments, which are contiguous subsequences of a chain.
#
# Functions are provided to parse domains using different domain parsing
# programs and return the corresponding list of PTDomain objects.
# Supported so far is:
# . DDOMAIN (Zhou et al 2007 Protein Science 16:947-955) program
# . CATH (CATH Domall File (CDF) 2.0) file
#
###############################################################################
import os,sys
from Bio.PDB import * # only needed for DDomain when segment spans chains
from ptutils import cleanup_tmpdir,get_int_icode
from ptsecstruct import PTSecStruct
#-----------------------------------------------------------------------------
#
# Module globals
#
#-----------------------------------------------------------------------------
verbose = False
#-----------------------------------------------------------------------------
#
# Class definitions
#
#-----------------------------------------------------------------------------
#
# Empty classes for exceptions
#
class NotInCATH_Exception(Exception): # identifier not found in CATH CDF file
pass
#
# Real classes
#
class PTSegment:
"""
The PTSegment object represents a segment (contiguous subsequence of
a chain) by the chain identifier, and start and end residue sequence
numbers
Note residue numbers here are integers, not PDB residue
'numbers' which are actually strings and may have insertion
codes, are not sequential (may have gaps, run backwards, etc.);
unlike in PTNode, PTGraph2 etc. where we store the PDB residue numbers,
(but use dictionary to get sequence integers when necesary).
Using integers makes everything much
simpler, can do calculations easily in domeval.py etc.
For this to work strictly correctly, should use proper purely sequential
numbering in these classes (PTSegment, PTDomain), such as that assigned
by DSSP or STRIDE, or equivalently by using the index into list of
residues built by Polypeptide Builder or similar from Bio.PDB.
"""
def __init__(self, chainid, start_resnum, end_resnum):
"""
Construct segment with supplied chain identifier and start and
end residue sequence numbers.
Parameters:
chainid - PDB chain identifier (may be '-' for none)
start_resnum - start residue sequence number
end_resnum - end residue sequence number
Exceptions:
raises ValueEror if end_resnum < start_resnum
"""
if end_resnum < start_resnum:
raise ValueError("end residue seqnum " + str(end_resnum) + " < " +
"start residue seqnum " + str(start_resnum))
self.chainid = chainid
self.start_resnum = start_resnum
self.end_resnum = end_resnum
def __str__(self):
"""
Return string representation of segment as 'chainid:start-chainid:end'
"""
return self.chainid + ":" + str(self.start_resnum) + \
"-" + self.chainid + ":" + str(self.end_resnum)
def is_in_segment(self, res_seq_num):
"""
Return True iff the supplied residue sequence number is in the
interval spanned by this segment (assumed to be same chainid)
Parameters:
res_seq_num - PDB residue sequence number to test
Return value:
True if res_seq_num is in >=start_resnum and <=end_resnum else False
"""
if res_seq_num >= self.start_resnum and \
res_seq_num <= self.end_resnum:
return True
else:
return False
# we will define only the rich comparison operators __eq__ and __ne__
# (not __le__, __gt__, etc.) to test for equality or non-equality
# of segments only.
def __eq__(self, other):
"""
Two segments are equal if they have same chain and same start and
end residues
"""
if (self.chainid == other.chainid and
self.start_resnum == other.start_resnum and
self.end_resnum == other.end_resnum):
return True
else:
return False
def __ne__(self,other):
"""
Two segments are '!=' (or '<>') exactly when they are not equal.
"""
return not self.__eq__(other)
class PTDomain:
"""
The PTDomain object represents a protein domain by a list of segments.
Segments are contiguous subsequences of a chain, and so each is
represented by a chain identifier, and a start and end residue sequence
number.
The domain consisting of domainid == None and segment_list == None
is a special domain signifying a single-domain protein. This is used
because we don't want to have to specify multiple segments for multiple
chains in a single domain - a single domain protein should be treated
just as one unit without worrying about dividing anything up.
"""
def __init__(self, domainid, segment_list):
"""
Create new PTDomain with supplied domain identifier and segment list.
Parameters:
domainid -domain identifier, string
segment_list - list of PTSegment objects
NOTE if both parameters are None this marks this PTDomain as the
one used as single element in domain list to signify single-domain
protein with no further information.
"""
self.domainid = domainid
self.segment_list = segment_list
def __str__(self):
"""
Return a representation of the domain as list of segments
separated by ';'
"""
if self.domainid == None and self.segment_list == None:
return "SINGLE-DOMAIN"
s = ""
for i in range(len(self.segment_list)):
s += str(self.segment_list[i])
if i < len(self.segment_list) - 1:
s += ';'
return s
def is_single(self):
"""
Return True iff this is the domain with no information representing
a single-domain protein
"""
if self.domainid == None and self.segment_list == None:
return True
else:
return False
def is_in_domain(self, chainid, res_seq_num):
"""
Return True iff the supplied residue specified by chainid and
residue sequence number is in this domain.
Parameters:
chainid - chainid of the residue to test
res_seq_num - PDB residue sequence number of the residue to test
Return value:
True if the residue is in this domain, else False.
"""
# Note we just do a linear search, no dictionaries or anything,
# as there is only have a maximum of maybe 5 domains, and usually
# only 1 or 2.
if self.is_single():
return True # always in the special 'single domain'
else:
for segment in self.segment_list:
if segment.chainid == chainid and \
segment.is_in_segment(res_seq_num):
return True
return False
def get_segments_for_chainid(self, chainid):
"""
Return a list of segments of the supplied chain in this domain
Parameters:
chainid - id of chain to find segments of
Return value:
list of PTSegment objects with supplied chain id
"""
if self.is_single():
return []
else:
return [f for f in self.segment_list if f.chainid == chainid]
def get_minmax_res_seq_in_chain(self, chainid):
"""
Return a tuple with the lowest and highest residue sequence numbers
in the supplied chainid in this domain.
Parameters:
chainid - chain id to find low and high residue sequence numbers for
Return value:
tuple (min_res_seq, max_res_seq) of the lowest and highest
respectively residue sequence numbers in the supplied chainid
in this domain.
"""
max_res_seq = 0
min_res_seq = sys.maxint
for segment in self.get_segments_for_chainid(chainid):
if segment.start_resnum < min_res_seq:
min_res_seq = segment.start_resnum
if segment.end_resnum > max_res_seq:
max_res_seq = segment.end_resnum
return (min_res_seq, max_res_seq)
def get_chainids(self):
"""
Return a list of all chain identifiers in this domain.
Parameters:
None.
Return value:
List of chain identifiers used by segments in this domain.
(Each chain identifier appears only once in list).
"""
chaindict = {} # dict of { chainid : True } (value not used)
if self.segment_list != None:
for segment in self.segment_list:
chaindict[segment.chainid] = True
return chaindict.keys()
def add_segment(self, segment):
"""
Add a segment to this domain.
Parameters:
segment - PTSegement to add to the domain
Return value: None
Modifies member data: segment_list
"""
if self.segment_list == None:
self.segment_list = [segment]
else:
self.segment_list.append(segment)
def remove_segment(self, segment):
"""
Remove a segment from this domain.
This may involve either simply removing a segment if there is one
in the domain that corresponds exactly to the supplied segment to
remove, otherwise the range of residues in the segment to remove
must be deleted from some existing segment resulting in a smaller
segment; a more complicated case can arise when the segment to
remove spans two (or more) segments (either entirely or in part).
Parameters:
segment - PTSegment representing segment (continguous range of
residues in a chain) to remove.
Return value: None
Modifies member data: segment_list, and segments in the list
"""
try:
sindex = self.segment_list.index(segment)
self.segment_list.pop(sindex)
except ValueError:
# no segment equal to supplied one found
# so look for a segment that entirely contains the one to remove
found = False
for cur_seg in self.segment_list:
if (segment.chainid == cur_seg.chainid and
segment.start_resnum >= cur_seg.start_resnum and
segment.end_resnum <= cur_seg.end_resnum):
found = True
break
if found:
if (segment.start_resnum == cur_seg.start_resnum):
cur_seg.start_resnum = segment.end_resnum + 1
elif (segment.end_resnum == cur_seg.end_resnum):
cur_seg.end_resnum = segment.start_resnum - 1
else:
# need to split segment in two which we will do
# by shortening existing segment for first part
# and creating new segment for later part
new_seg = PTSegment(cur_seg.chainid,
segment.end_resnum + 1,
cur_seg.end_resnum)
cur_seg.end_resnum = segment.start_resnum - 1
self.segment_list.append(new_seg)
else:
# segment is not found at all or extends ouside of a
# segment in the list
for cur_seg in self.segment_list:
if (segment.chainid == cur_seg.chainid):
if (segment.start_resnum >= cur_seg.start_resnum and
segment.start_resnum <= cur_seg.end_resnum):
# extends over end of cur_seg: shorten cur_seg
# to end at start of segment
cur_seg.end_resnum = segment.start_resnum - 1
elif (segment.end_resnum <= cur_seg.end_resnum and
segment.end_resnum >= cur_seg.start_resnum):
# extends over start of cur_seg: shorten
# cur_seg to start and end of segment
cur_seg.start_resnum = segment.end_resnum + 1
#-----------------------------------------------------------------------------
#
# Function definitions
#
#-----------------------------------------------------------------------------
def read_domains_from_ddomain(pdb_filename, pdb_model, chainid=None):
"""
Use the DDOMAIN program to parse the structure from a PDB file into
domains and return the corresponding list of PTDomain objects.
DDOMAIN is described in
Zhou, Xue, Zhou 2007 'DDOMAIN: Dividing structures into domains using a
normalized domain-domain interaction profile' Protein Science 16:947-955.
It is available as a 64-bit linux executable and FORTRAN-77 source code
from http://sparks.informatics.iupui.edu/Resource_files/DDOMAIN.tar.gz
Parameters:
pdb_filename - filename of PDB file to run DDOMAIN on
pdb_model - Bio.PDB model struct for this PDB entry. Note that this
is only needed in the case that a DDomain domain has
different chain identifiers for start and end and
is then used just to find last residue number in chain.
chainid - (default None). If not None, only the specified chain
is requested.
Return value:
List of PTDomain objects, one for each domain.
NOTE: if there is only one domain, we will return a list with a single
PTDomain with all data None, signifying a single domain protein
with no further information.
This is mainly because of when
there are multiple chains, in which case the single domain is reported
by DDOMAIN as having a different chain id for start and end. If there
is a single domain we really don't want to do anything special, so
it is better to just have it as a special case where no domain processing
is done.
"""
# DDOMAIN needs the PDB file in its working directory, and it reads
# the PDB code (e.g. 1QLP for PDB file 1QLP.pdb) from stdin
# (optionaly with chain suffix, which we won't use)
# Note it requires this filename format, so for format like pdb1qlp.ent
# we need to rename the file for DDOMAIN to 1QLP.pdb
# This is nasty, but otherwise have to modify DDOMAIN FORTRAN-77 source
# so that's even more hassle to have to have a custom version (like we
# did with STRIDE).
# So we'll work in /tmp directory, make a symlink (TODO: only UNIX allows
# this, maybe should actually copy file so works on other platforms)
# and run DDOMAIN there.
oldcwd = os.getcwd()
TMPDIR = os.tempnam(None, "ptdd")
os.mkdir(TMPDIR)
symlink_path = None
try:
pdb_file_basename = os.path.basename(pdb_filename)
(name,extension) = os.path.splitext(pdb_file_basename)
if extension.lower() == '.pdb': # e.g. 1QLP.pdb
pdb_identifier = name
pdb_file_directory = os.path.split(pdb_filename)[0]
symlink_path = os.path.join(TMPDIR, pdb_file_basename)
os.symlink(os.path.abspath(pdb_filename), symlink_path)
elif extension != '.ent' or name[:3].lower() != 'pdb':
sys.stderr.write('WARNING: unknown PDB filename format "'
+ pdb_file_basename + '"\n')
sys.stderr.write(' Not running DDomain\n')
domain_list = [PTDomain(None, None)] # one-domain protein, no further info
return domain_list
else: # e.g. pdb1qlp.ent, make a symlink to it in format 1QLP.pdb
pdb_identifier = name[3:7].upper()
symlink_path = os.path.join(TMPDIR, pdb_identifier + '.pdb')
os.symlink(os.path.abspath(pdb_filename), symlink_path)
os.chdir(TMPDIR)
if verbose:
sys.stderr.write("running DDomain...")
(ddomain_stdin, ddomain_stdout) = os.popen2("DDomain")
if chainid != None:
pdbchainid = pdb_identifier + chainid
else:
pdbchainid = pdb_identifier
ddomain_stdin.write(pdbchainid + '\n')
ddomain_stdin.close()
domain_list = parse_ddomain_output(ddomain_stdout, pdb_model)
ddomain_stdout.close()
if verbose:
sys.stderr.write("done\n")
finally:
if symlink_path:
os.unlink(symlink_path)
os.chdir(oldcwd)
cleanup_tmpdir(TMPDIR)
return domain_list
def parse_ddomain_output(fh, pdb_model):
"""
Parse the output of the DDOMAIN program.
DDOMAIN is described in
Zhou, Xue, Zhou 2007 'DDOMAIN: Dividing structures into domains using a
normalized domain-domain interaction profile' Protein Science 16:947-955.
It is available as a 64-bit linux executable and FORTRAN-77 source code
from http://sparks.informatics.iupui.edu/Resource_files/DDOMAIN.tar.gz
Parameters:
fh - filehandle to read DDOMAIN output from (alrady open for read)
pdb_model - Bio.PDB model struct for this PDB entry. Note that this
is only needed in the case that a DDomain domain has
different chain identifiers for start and end and
Return value:
List of PTDomain objects, one for each domain.
"""
domain_list = []
# Output looks like this:
#
# AUTHORS-trained parameters
# 1 A 3 A 109
# 2 B 3 B 109
# SCOP-trained parameters
# 1 A 3 A 109
# 2 B 3 B 109
# CATH-trained parameters
# 1 A 3 A 109
# 2 B 3 B 109
#
# We will use the AUTHORS-trained parameters output (see paper).
# Note also that DDOMAIN only allows domains to be a single
# continguous subsequence of chain anyway (i.e. not multiple
# segments) (see paper), so we only ever have one segment in a
# domain from this function.
# It can, however, have a segment that includes parts of two chains
# i.e. runs off one chain and includes (part of) another chain.
# E.g. 1BAR:
#
# AUTHORS-trained parameters
# 1 A 11 B 7
# 2 B 8 B 138
readout = False
for line in fh:
if line.strip()[0:8] == "AUTHORS-":
readout = True
continue
elif line.strip()[0:5] == "SCOP-" or line.strip()[0:5] == "CATH-":
break #finished with output
if readout:
splitline = line.split()
if len(splitline) == 5:
(domain_id,chainid_start,resnum_start,chainid_end,resnum_end) =\
splitline
elif len(splitline) == 3:
# chain identifier is ' ' (space) so convert to '-' (dash)
domain_id = splitline[0]
chainid_start = '-'
resnum_start = splitline[1]
chainid_end = '-'
resnum_end = splitline[2]
else:
sys.stderr.write(
'WARNING: error parsing line DDOMAIN output line:\n'
+ line)
continue
resnum_start = int(resnum_start)
resnum_end = int(resnum_end)
if resnum_start < 0:
resnum_start = 0
sys.stderr.write(
'WARNING: DDomain negative residue start number, set to 0\n')
if resnum_end < 0:
resnum_end = 0
sys.stderr.write(
'WARNING: DDomain negative residue end number, set to 0\n')
if chainid_start != chainid_end:
sys.stderr.write('WARNING: DDomain different chainid in domain'+
' ' + str(domain_id) +
': splitting into two segments\n')
# DDomain (as of Sep 2007) cannot have multiple segments in a
# domain but it does sometimes have a different chain id for
# start and end in a domain meaning (I think) that the
# segment consists of the first chain from start residue up
# to C-terminus and second chain from N-terminus up to
# end residue (in that second chain).
# So we make two segments, one in each of the two chains,
# for this domain.
start_chain = pdb_model[chainid_start]
end_chain = pdb_model[chainid_end]
# id of a residue in Bio.PDB is tuple (hetatm, resseqnum, icode)
startchain_res_seqnum_list = [ res.get_id()[1] for res in
start_chain.get_list()
if res.get_id()[0] == ' ' ]
max_startchain_resnum = max(startchain_res_seqnum_list)
endchain_res_seqnum_list = [ res.get_id()[1] for res in
end_chain.get_list()
if res.get_id()[0] == ' ' ]
min_endchain_resnum = min(endchain_res_seqnum_list)
segment1 = PTSegment(chainid_start, resnum_start,
max_startchain_resnum)
segment2 = PTSegment(chainid_end, min_endchain_resnum,
resnum_end)
domain = PTDomain(domain_id, [segment1, segment2])
domain_list.append(domain)
else:
if resnum_start > resnum_end:
# This happens e.g. on 1BMV chain 2 (only if chain 2
# only is requested). Don't know what it means really,
# but let's make sure we don't get an exception anyway
sys.stderr.write('WARNING: DDomain start resnum ' +
str(resnum_start) + ' > end resnum ' +
str(resnum_end) + ', swapping.\n')
tmp = resnum_start
resnum_start = resnum_end
resnum_end = tmp
segment = PTSegment(chainid_start, resnum_start, resnum_end)
domain = PTDomain(domain_id, [segment])
domain_list.append(domain)
return domain_list
def read_domains_from_cath_cdf_file(cdf_filename, pdbid, chainid=None):
"""
Read the domain decomposition from the CATH Domall File (CDF)
whose filename is supplied.
These files and their description can be found at
ftp://ftp.biochem.ucl.ac.uk/pub/cathdata/v3.1.0
specifically the files README.CDF_FORMAT_2.0 for the description and
the actual CDF file CathDomall.v3.1.0
See http://www.cathdb.info/ for CATH in general.
Parameters:
cdf_filename - filename of the CATH CDF file to read
pdbid - pdb identifier to read domains for
chainid - (default None). If not None, get only this chain.
Return value:
List of PTDomain objects, one for each domain.
Raises exceptions:
NOTE: if the pdb id is not found in the file, we will return a list
raise the NotInCATH_Exception
"""
domain_list = []
issued_warning = False
# This is from README_CDF_FORMAT_2.0:
#
# KEY:
# N = Number of segments
# C = Chain character
# I = Insert character/code ('-' indicates no insert character)
# S = Start PDB number
# E = End PDB number
# NR = number of residues (fragment information only)
#
# 1chmA D02 F00 1 A 2 - A 156 - 1 A 157 - A 402 -
# N |C S I C E I| N |C S I C E I|
# |<----Domain One---->|<-----Domain Two---->|
# |<--Segment One-->| |<--Segment One-->|
#
# This translates to:
# 1chmA01 = Chain A; 2-156
# 1chmA02 = Chain A; 157-402
found = False
cdf_fh = open(cdf_filename)
for line in cdf_fh:
line = line.lstrip().upper()
if line[0] == '#':
continue # skip comment lines
cdf_rec = line.split()
# CDF file appears to be sorted by PDB id ascending so we could
# do a binary search or at least shortcut this loop but don't
# really want to depend on it so won't bother.
chain_name = cdf_rec[0] # 5 chars e.g. 1chmA
if ( chain_name[:4] == pdbid.upper() and # matches our PDB identifier
(chainid == None or chain_name[4] == chainid) ): # and chainid
found = True
if cdf_rec[1][0] != 'D' or cdf_rec[2][0] != 'F':
sys.stderr.write('WARNING: bad CDF record ignored: ' +
line + '\n')
continue
num_domains = int(cdf_rec[1][1:])
num_fragments = int(cdf_rec[2][1:])
field = 3 # fields 0,1,2 where chainname, domains, fragments
for domnum in range(num_domains):
# CDF records actually have a different line for each
# chain and so each chain is always a new domain in this
# format it would appear. So we will name the domains
# with chain identifier AND domain number so they are unique.
# Though it seems not quite right that a new chain means
# a new domain - domains should be able to contain multiple
# segments of (different) chains but CDF records seem
# to maintain chains as the higher level of hierarchy
# (chain being part of the record identifier ie chainid
# = pdbid + chainid, the first field).
domain_id = (chain_name[4] + # chain identifier
str(domnum + 1)) # number from one not zero
segment_list = []
num_segments = int(cdf_rec[field])
field += 1
for segnum in range(num_segments):
start_chainchar = cdf_rec[field]
field += 1
start_pdbnum = int(cdf_rec[field])
field += 1
insertcode1 = cdf_rec[field]
field += 1
end_chainchar = cdf_rec[field]
field += 1
end_pdbnum = int(cdf_rec[field])
field += 1
insertcode2 = cdf_rec[field]
field += 1
if start_chainchar != end_chainchar or \
start_chainchar != chain_name[4]:
# TODO: I think this never happens, but should
# do something with it anyway
sys.stderr.write('WARNING: mismatch chain characters '+
'in CDF record: ' + line + '\n')
if start_chainchar == '0': # blank chainid in (old) PDB recs
if not issued_warning:
sys.stderr.write('WARNING: '\
'CDF record ' + chain_name +\
' indicates no chain field '\
'in PDB record. '\
'(Will not work with'\
' remediated (2007) PDB files).\n'
' Changing chain to A.\n')
issued_warning = True
start_chainchar = 'A'
if start_pdbnum > end_pdbnum:
# This happens e.g. on 1BMV chain 2 (only if chain 2
# only is requested). Don't know what it means really,
# but let's make sure we don't get an exception anyway
sys.stderr.write('WARNING: CATH start resnum ' +
str(start_pdbnum) + ' > end resnum ' +
str(end_pdbnum) + ', swapping.\n')
tmp = start_pdbnum
start_pdbnum = end_pdbnum
end_pdbnum = tmp
segment_list.append(PTSegment(
cdf_chainid_to_stride_chainid(start_chainchar),
start_pdbnum, end_pdbnum))
domain_list.append(PTDomain(domain_id, segment_list))
# we don't do anything with what CDF terms 'fragments'
if not found:
raise NotInCATH_Exception(pdbid)
cdf_fh.close()
if len(domain_list) > 0:
return domain_list
else:
return [PTDomain(None, None)] # one-domain protein, no further info
def cdf_chainid_to_stride_chainid(cdf_chainid):
"""
Convert a CDF (CATH Domall File) chainid to a STRIDE chainid.
STRIDE uses '-' for a 'blank' chainid while PDB uses ' ' (space)
and CATH (CDF) uses '0' where PDB has a blank (space) chain identfifer.
We use the STRIDE convention ('-') in this program.
So all this does is return the cdf_chainid unless it is '0', then
it returns '-'.
Parameters:
pdb_chainid - the PDB chain identifier
Return value:
STRIDE chain identifier corresponding to supplied pdb_chainid
"""
if cdf_chainid == '0':
return '-'
else:
return cdf_chainid
def ptdomain_set_verbose(verb):
"""
set the module global verbose flag in this module to supplied value
Parameters: verb - True (for verbose output) or False
Return value: None
Uses globals: verbose (in this module)
"""
global verbose
verbose = verb
def fixup_crossdomain_sses(secstruct, domain_list):
"""
Find any SSEs that span a domain boundary, and put each entirely
in one domain.
The domain is chosen as the one that contains most of the residues
int the SSE.
Parameters:
secstruct - PTSecStruct (ptsecstruct.py) object descirbing SSEs
domain_list - list of PTDomain objects representing all the domains
in this protein.
(in/out) NOTE: may be modified by having a segment
removed from a domain if SSE is only partly in
the domain.
Return value: None.
"""
sse_list = ( [(start_chainid, start_resnum, end_chainid, end_resnum)
for (start_chainid, start_resnum, end_chainid, end_resnum)
in secstruct.strand_list] +
[(start_chainid, start_resnum, end_chainid, end_resnum)
for (start_chainid, start_resnum, end_chainid, end_resnum, helix_type)
in secstruct.helix_list] )
for (start_chainid, start_resnum, end_chainid, end_resnum) in sse_list:
for domain in domain_list:
if (domain.is_in_domain(start_chainid,
get_int_icode(start_resnum)[0])
and not domain.is_in_domain(end_chainid,
get_int_icode(end_resnum)[0]) ):
# This really shouldn't happen, but does: domain
# decomposition has determined that this SSE crosses
# a domain boundary (really our SSE decisions don't
# match whatever domain decomposition has done).
# We'll have to assign the SSE to
# a domain, and add the residues it spans into that
# domain.
# find domain2 as the other domain the SSE is also in
for domain2 in domain_list:
if domain2 == domain:
continue
if domain2.is_in_domain(end_chainid,
get_int_icode(end_resnum)[0]):
break
# find sse_domain as the domain with more residues of the
# SSE in it
domain_res_count = 0
domain2_res_count = 0
# FIXME: this is ignoring insertion codes etc., really
# should convert to proper sequential residue sequence numbers
# to do this
start_resint = get_int_icode(start_resnum)[0]
end_resint = get_int_icode(end_resnum)[0]
for resint in range(start_resint, end_resint+1):
if domain.is_in_domain(start_chainid, resint):
domain_res_count += 1
elif domain2.is_in_domain(start_chainid, resint):
domain2_res_count += 1
else:
sys.stderr.write('ERROR: SSE in more than 2 domains\n')
if domain2_res_count > domain_res_count:
sse_domain = domain2
else:
sse_domain = domain # arbitrarily domain if equal count
# first remove the segment from where it currently is
seg = PTSegment(start_chainid,
get_int_icode(start_resnum)[0],
get_int_icode(end_resnum)[0])
# print 'xxx',str(seg)
for dom in domain_list:
# print 'aaa',str(dom)
dom.remove_segment(seg)
# print 'bbb',str(dom)
sys.stderr.write('WARNING: SSE ' + start_chainid + ':' +
start_resnum + '-' + end_resnum +
' crosses domain boundary.\n'
' Put in domain ' + sse_domain.domainid +
' (' + str(sse_domain) + ').\n')
sse_domain.add_segment(seg)
# print 'zzz',str(sse_domain)
break # no need to look at any more domains for this SSE
# # DEBUG
# for i in range(len(domain_list)):
# sys.stdout.write(str(domain_list[i]))
# if i < len(domain_list) - 1:
# sys.stdout.write('/')
# sys.stdout.write('\n')
# # END DEBUG