melodia-py 0.1.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of melodia-py might be problematic. Click here for more details.
- melodia_py/__init__.py +7 -0
- melodia_py/clustering.py +582 -0
- melodia_py/data/luthier.dat +198 -0
- melodia_py/geometryparser.py +602 -0
- melodia_py/main.py +20 -0
- melodia_py/melodia_py.py +851 -0
- melodia_py-0.1.2.dist-info/LICENSE +202 -0
- melodia_py-0.1.2.dist-info/METADATA +83 -0
- melodia_py-0.1.2.dist-info/RECORD +11 -0
- melodia_py-0.1.2.dist-info/WHEEL +5 -0
- melodia_py-0.1.2.dist-info/top_level.txt +1 -0
melodia_py/__init__.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
from .melodia_py import geometry_from_structure_file, geometry_dict_from_structure, dataframe_from_alignment
|
|
2
|
+
from .melodia_py import geometry_from_structure, bfactor_from_geo, view_putty, view_cartoon, view_tube
|
|
3
|
+
from .melodia_py import PropensityTable, parser_pir_file, cluster_alignment, save_align_to_ps, save_pymol_script
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
__version__ = '0.1.0'
|
|
7
|
+
__author__ = 'Rinaldo Wander Montalvão, PhD'
|
melodia_py/clustering.py
ADDED
|
@@ -0,0 +1,582 @@
|
|
|
1
|
+
# Copyright 2021-2024 KU Leuven.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
#
|
|
15
|
+
# Author: Rinaldo Wander Montalvão, PhD
|
|
16
|
+
#
|
|
17
|
+
import math
|
|
18
|
+
import warnings
|
|
19
|
+
|
|
20
|
+
import numpy as np
|
|
21
|
+
|
|
22
|
+
from sty import fg
|
|
23
|
+
|
|
24
|
+
from numpy.random import rand, randint
|
|
25
|
+
|
|
26
|
+
from Bio.PDB import PDBParser, PDBIO
|
|
27
|
+
from Bio.SeqUtils import seq1
|
|
28
|
+
from Bio.SVDSuperimposer import SVDSuperimposer
|
|
29
|
+
from Bio.PDB.PDBExceptions import PDBConstructionWarning
|
|
30
|
+
|
|
31
|
+
from sklearn.preprocessing import StandardScaler
|
|
32
|
+
from sklearn.cluster import AgglomerativeClustering
|
|
33
|
+
|
|
34
|
+
warnings.filterwarnings('ignore', category=PDBConstructionWarning)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def rmsd(x: np.ndarray, y: np.ndarray) -> float:
|
|
38
|
+
"""
|
|
39
|
+
Compute the RMSD between the two numpy arrays.
|
|
40
|
+
:param x: The first numpy array
|
|
41
|
+
:type x: np.ndarray
|
|
42
|
+
:param y: The second numpy array
|
|
43
|
+
:type y: np.ndarray
|
|
44
|
+
:return: The RMSD between the two numpy arrays
|
|
45
|
+
:rtype: float
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
return np.sqrt((((x - y) ** 2) * 3).mean())
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def select(data: np.ndarray, seg: list, msk: list) -> np.ndarray:
|
|
52
|
+
"""
|
|
53
|
+
Select the data in the given segment from the mask.
|
|
54
|
+
:param data: The data to select
|
|
55
|
+
:type data: np.ndarray
|
|
56
|
+
:param seg: The segment to select
|
|
57
|
+
:type seg: list
|
|
58
|
+
:param msk: The mask for aligned segment
|
|
59
|
+
:type msk: list
|
|
60
|
+
:return: The selected data
|
|
61
|
+
:rtype: np.ndarray
|
|
62
|
+
"""
|
|
63
|
+
tmp = []
|
|
64
|
+
for i, j in enumerate(seg):
|
|
65
|
+
if msk[i]:
|
|
66
|
+
tmp.append(data[j])
|
|
67
|
+
|
|
68
|
+
return np.array(tmp)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def superposition(xo: np.ndarray, yo: np.ndarray, seg: list, msk: list) -> tuple[np.ndarray, np.ndarray]:
|
|
72
|
+
"""
|
|
73
|
+
Compute the superposition of the two numpy arrays.
|
|
74
|
+
:param xo: The first numpy array
|
|
75
|
+
:type xo: np.ndarray
|
|
76
|
+
:param yo: The second numpy array
|
|
77
|
+
:type yo: np.ndarray
|
|
78
|
+
:param seg: The segment to superimpose
|
|
79
|
+
:type seg: list
|
|
80
|
+
:param msk: The mask for the aligned segment
|
|
81
|
+
:type msk: list
|
|
82
|
+
:return: The superposition of the two numpy arrays
|
|
83
|
+
:rtype: tuple(np.ndarray, np.ndarray)
|
|
84
|
+
"""
|
|
85
|
+
x = select(xo, seg, msk)
|
|
86
|
+
y = select(yo, seg, msk)
|
|
87
|
+
|
|
88
|
+
sup = SVDSuperimposer()
|
|
89
|
+
|
|
90
|
+
sup.set(x, y)
|
|
91
|
+
sup.run()
|
|
92
|
+
|
|
93
|
+
return sup.get_rotran()
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def energy(xo: np.ndarray, yo: np.ndarray, seg: list, msk: list) -> float:
|
|
97
|
+
"""
|
|
98
|
+
Compute the energy between two numpy arrays.
|
|
99
|
+
:param xo: The first numpy array
|
|
100
|
+
:type xo: np.ndarray
|
|
101
|
+
:param yo: The second numpy array
|
|
102
|
+
:type yo: np.ndarray
|
|
103
|
+
:param seg: The segment to superimpose
|
|
104
|
+
:type seg: list
|
|
105
|
+
:param msk: The mask for the aligned segment
|
|
106
|
+
:type msk: list
|
|
107
|
+
:return: The energy between two numpy arrays
|
|
108
|
+
:rtype: float
|
|
109
|
+
"""
|
|
110
|
+
rot, tran = superposition(xo, yo, seg, msk)
|
|
111
|
+
|
|
112
|
+
yt = np.dot(yo, rot) + tran
|
|
113
|
+
|
|
114
|
+
return rmsd(xo, yt)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def segments(anchors: list, members: list) -> list:
|
|
118
|
+
"""
|
|
119
|
+
Return a list of segments for each member of the protein alignment of an anchor.
|
|
120
|
+
:param anchors: The list of anchors
|
|
121
|
+
:type anchors: list
|
|
122
|
+
:param members: The list of members
|
|
123
|
+
:type members: list
|
|
124
|
+
:return: The of segments
|
|
125
|
+
:rtype: list
|
|
126
|
+
"""
|
|
127
|
+
seg = []
|
|
128
|
+
for member in members:
|
|
129
|
+
ini, end = anchors[member]
|
|
130
|
+
seg.extend([x for x in range(ini, end)])
|
|
131
|
+
|
|
132
|
+
return seg
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def simulated_annealing(xo: np.ndarray,
|
|
136
|
+
yo: np.ndarray,
|
|
137
|
+
anchors: list,
|
|
138
|
+
members: list) -> tuple[np.ndarray, np.ndarray, float]:
|
|
139
|
+
"""
|
|
140
|
+
Compute the rotation and translation matrices that reduces the RMSD
|
|
141
|
+
between two protein segments.
|
|
142
|
+
:param xo: The first numpy array
|
|
143
|
+
:type xo: np.ndarray
|
|
144
|
+
:param yo: The second numpy array
|
|
145
|
+
:type yo: np.ndarray
|
|
146
|
+
:param anchors: The list of anchors
|
|
147
|
+
:type anchors: list
|
|
148
|
+
:param members: The list of members
|
|
149
|
+
:type members: list
|
|
150
|
+
:return: The rotation and translation matrices and the final RMSD
|
|
151
|
+
:rtype: tuple(np.ndarray, np.ndarray, float)
|
|
152
|
+
"""
|
|
153
|
+
seg = segments(anchors, members)
|
|
154
|
+
|
|
155
|
+
msk0 = [True for _ in range(len(seg))]
|
|
156
|
+
|
|
157
|
+
temperature = 300
|
|
158
|
+
energy0 = energy(xo, yo, seg, msk0)
|
|
159
|
+
while temperature > 0.00001:
|
|
160
|
+
for i in range(10000):
|
|
161
|
+
msk1 = msk0.copy()
|
|
162
|
+
j = randint(len(msk1))
|
|
163
|
+
if msk1[j]:
|
|
164
|
+
msk1[j] = False
|
|
165
|
+
else:
|
|
166
|
+
msk1[j] = True
|
|
167
|
+
|
|
168
|
+
energy1 = energy(xo, yo, seg, msk1)
|
|
169
|
+
|
|
170
|
+
energy_delta = energy1 - energy0
|
|
171
|
+
|
|
172
|
+
if energy_delta < 0.0 or rand() < math.exp(-energy_delta / temperature):
|
|
173
|
+
msk0 = msk1.copy()
|
|
174
|
+
energy0 = energy1
|
|
175
|
+
|
|
176
|
+
# TODO: test other schedules
|
|
177
|
+
temperature *= 0.1
|
|
178
|
+
|
|
179
|
+
rot, tran = superposition(xo, yo, seg, msk0)
|
|
180
|
+
return rot, tran, energy0
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def superimposer(align, threshold=0.8, csv=False, pdb=True):
|
|
184
|
+
"""
|
|
185
|
+
Given an alignment, superimpose the PDB structure using Differential Geometry
|
|
186
|
+
as dissimilarity measure. It can output CSV files with the alpha carbon coordinates
|
|
187
|
+
and cluster groups, and also save the superimposed PDB structures.
|
|
188
|
+
:param align: Alignment to superimpose
|
|
189
|
+
:type align: Bio.Align.MultipleSeqAlignment
|
|
190
|
+
:param threshold: Threshold for cluster selection
|
|
191
|
+
:type threshold: float
|
|
192
|
+
:param csv: If true, output the CSV files
|
|
193
|
+
:type csv: bool
|
|
194
|
+
:param pdb: If true, output the superimposed PDB structures
|
|
195
|
+
:type pdb: bool
|
|
196
|
+
"""
|
|
197
|
+
# Collect Differential Geometry data for initial clustering
|
|
198
|
+
data = []
|
|
199
|
+
id2pos = {}
|
|
200
|
+
|
|
201
|
+
for position, record in enumerate(align):
|
|
202
|
+
if 'structure' in record.description:
|
|
203
|
+
id2pos[record.id] = position
|
|
204
|
+
pairs = zip(
|
|
205
|
+
record.letter_annotations['curvature'],
|
|
206
|
+
record.letter_annotations['torsion'],
|
|
207
|
+
)
|
|
208
|
+
data = [list(pair) for pair in pairs]
|
|
209
|
+
record.letter_annotations['cluster'] = [0 for _ in record.seq]
|
|
210
|
+
|
|
211
|
+
scaler = StandardScaler()
|
|
212
|
+
scaler.fit(data)
|
|
213
|
+
|
|
214
|
+
# Clustering to find maximal conserved regions
|
|
215
|
+
clustering = AgglomerativeClustering(distance_threshold=threshold, n_clusters=None)
|
|
216
|
+
for i in range(align.get_alignment_length()):
|
|
217
|
+
xy = []
|
|
218
|
+
tags = []
|
|
219
|
+
for identity, position in id2pos.items():
|
|
220
|
+
record = align[position]
|
|
221
|
+
if record.seq[i] != '-':
|
|
222
|
+
xy.append(
|
|
223
|
+
[
|
|
224
|
+
record.letter_annotations['curvature'][i],
|
|
225
|
+
record.letter_annotations['torsion'][i],
|
|
226
|
+
]
|
|
227
|
+
)
|
|
228
|
+
tags.append(identity)
|
|
229
|
+
|
|
230
|
+
if len(xy) > 1:
|
|
231
|
+
xy = scaler.transform(xy)
|
|
232
|
+
|
|
233
|
+
clusters = clustering.fit_predict(xy)
|
|
234
|
+
|
|
235
|
+
map_of_clusters = {pair[0]: pair[1] for pair in zip(tags, clusters)}
|
|
236
|
+
|
|
237
|
+
for identity, position in id2pos.items():
|
|
238
|
+
record = align[position]
|
|
239
|
+
if identity in map_of_clusters:
|
|
240
|
+
record.letter_annotations['cluster'][i] = (
|
|
241
|
+
map_of_clusters[identity] + 1
|
|
242
|
+
)
|
|
243
|
+
# print(id, record.letter_annotations['cluster'][i])
|
|
244
|
+
else:
|
|
245
|
+
for identity, position in id2pos.items():
|
|
246
|
+
record = align[position]
|
|
247
|
+
record.letter_annotations['cluster'][i] = 0
|
|
248
|
+
|
|
249
|
+
# Find the highly conserved areas
|
|
250
|
+
ini = 0
|
|
251
|
+
end = align.get_alignment_length()
|
|
252
|
+
|
|
253
|
+
area = []
|
|
254
|
+
for i in range(ini, end):
|
|
255
|
+
s = set()
|
|
256
|
+
for identity, position in id2pos.items():
|
|
257
|
+
record = align[position]
|
|
258
|
+
j = record.letter_annotations['cluster'][i]
|
|
259
|
+
s = s.union({j})
|
|
260
|
+
# Only regions conserved across all proteins
|
|
261
|
+
if len(s) == 1:
|
|
262
|
+
area.append(1)
|
|
263
|
+
else:
|
|
264
|
+
area.append(0)
|
|
265
|
+
|
|
266
|
+
# Find all anchor conserved regions
|
|
267
|
+
anchors = []
|
|
268
|
+
anchor_lengths = []
|
|
269
|
+
new_area = [0 for _ in area]
|
|
270
|
+
|
|
271
|
+
ini, end = 0, 0
|
|
272
|
+
while ini < len(area):
|
|
273
|
+
while ini < len(area):
|
|
274
|
+
if area[ini] == 1:
|
|
275
|
+
break
|
|
276
|
+
else:
|
|
277
|
+
ini += 1
|
|
278
|
+
|
|
279
|
+
end = ini
|
|
280
|
+
while end < len(area):
|
|
281
|
+
if area[end] != 1:
|
|
282
|
+
break
|
|
283
|
+
else:
|
|
284
|
+
end += 1
|
|
285
|
+
|
|
286
|
+
# Minimal length for an anchor regions is 5 residues long
|
|
287
|
+
if end - ini >= 5:
|
|
288
|
+
anchor_lengths.append(end - ini)
|
|
289
|
+
anchors.append((ini, end))
|
|
290
|
+
for i in range(ini, end):
|
|
291
|
+
new_area[i] = 1
|
|
292
|
+
ini = end
|
|
293
|
+
|
|
294
|
+
# Group is the annotation for the new clusters
|
|
295
|
+
for identity, position in id2pos.items():
|
|
296
|
+
record = align[position]
|
|
297
|
+
record.letter_annotations['group'] = [0 for _ in record.seq]
|
|
298
|
+
record.letter_annotations['ca_coords'] = [[0.0, 0.0, 0.0] for _ in record.seq]
|
|
299
|
+
|
|
300
|
+
for anchor in anchors:
|
|
301
|
+
for identity, position in id2pos.items():
|
|
302
|
+
record = align[position]
|
|
303
|
+
ini, end = anchor
|
|
304
|
+
for i in range(ini, end):
|
|
305
|
+
record.letter_annotations['group'][i] = 1
|
|
306
|
+
|
|
307
|
+
# Select the top 5 anchor regions
|
|
308
|
+
top_anchors = np.argsort(anchor_lengths)[::-1]
|
|
309
|
+
if len(top_anchors) > 5:
|
|
310
|
+
top_anchors = top_anchors[:5]
|
|
311
|
+
|
|
312
|
+
# Read the CA coordinates for all the files
|
|
313
|
+
parser = PDBParser()
|
|
314
|
+
|
|
315
|
+
ca_coords = {}
|
|
316
|
+
ca_masked = {}
|
|
317
|
+
structures = {}
|
|
318
|
+
for record in align:
|
|
319
|
+
if 'structure' not in record.description:
|
|
320
|
+
continue
|
|
321
|
+
id = record.id
|
|
322
|
+
structures[id] = parser.get_structure(id, f'{id}.pdb')
|
|
323
|
+
|
|
324
|
+
model = structures[id][0]
|
|
325
|
+
xyz = []
|
|
326
|
+
seq = []
|
|
327
|
+
for chain in model:
|
|
328
|
+
for residue in chain:
|
|
329
|
+
xyz.append(residue['CA'].get_coord())
|
|
330
|
+
seq.append(seq1(residue.get_resname()))
|
|
331
|
+
j = 0
|
|
332
|
+
cds = []
|
|
333
|
+
msk = []
|
|
334
|
+
for i in range(align.get_alignment_length()):
|
|
335
|
+
# Insure coordinate alignment
|
|
336
|
+
if record.seq[i] == '-':
|
|
337
|
+
cds.append(np.array([0.0, 0.0, 0.0]))
|
|
338
|
+
msk.append(False)
|
|
339
|
+
elif record.seq[i] == seq[j]:
|
|
340
|
+
cds.append(xyz[j])
|
|
341
|
+
msk.append(True)
|
|
342
|
+
j += 1
|
|
343
|
+
else:
|
|
344
|
+
print(f'Error: {i} {record.seq[i]} - {j} {seq[j]}')
|
|
345
|
+
ca_coords[id] = np.array(cds)
|
|
346
|
+
ca_masked[id] = np.array(msk)
|
|
347
|
+
|
|
348
|
+
# Optimise the anchor positions for minimal RMSD
|
|
349
|
+
# TODO: Use a dendrogram for the order?
|
|
350
|
+
ids = list(ca_coords.keys())
|
|
351
|
+
ref = ids.pop(0)
|
|
352
|
+
print(f'{ref}')
|
|
353
|
+
|
|
354
|
+
xo = ca_coords[ref]
|
|
355
|
+
for idx in ids:
|
|
356
|
+
yo = ca_coords[idx]
|
|
357
|
+
rot, tran, E0 = simulated_annealing(xo, yo, anchors, top_anchors)
|
|
358
|
+
ca_coords[idx] = np.dot(yo, rot) + tran
|
|
359
|
+
print(f'{idx}: {E0:.2f} Å')
|
|
360
|
+
|
|
361
|
+
for model in structures[idx]:
|
|
362
|
+
for chain in model:
|
|
363
|
+
for residue in chain:
|
|
364
|
+
for atom in residue:
|
|
365
|
+
ya = atom.get_coord()
|
|
366
|
+
atom.set_coord(np.dot(ya, rot) + tran)
|
|
367
|
+
|
|
368
|
+
# Store the CA coordinates in the record
|
|
369
|
+
for idx in ca_coords.keys():
|
|
370
|
+
for i, m in enumerate(ca_masked[idx]):
|
|
371
|
+
if m:
|
|
372
|
+
record = align[id2pos[idx]]
|
|
373
|
+
x = ca_coords[idx][i][0]
|
|
374
|
+
y = ca_coords[idx][i][1]
|
|
375
|
+
z = ca_coords[idx][i][2]
|
|
376
|
+
record.letter_annotations['ca_coords'][i] = [x, y, z]
|
|
377
|
+
|
|
378
|
+
# Remove group outliers
|
|
379
|
+
for anchor in anchors:
|
|
380
|
+
ini, end = anchor
|
|
381
|
+
for i in range(ini, end):
|
|
382
|
+
max_dist = 0.0
|
|
383
|
+
for j in id2pos.keys():
|
|
384
|
+
cj = ca_coords[j][i]
|
|
385
|
+
for k in id2pos.keys():
|
|
386
|
+
if k != j:
|
|
387
|
+
ck = ca_coords[k][i]
|
|
388
|
+
dist = np.linalg.norm(ck - cj)
|
|
389
|
+
if dist > max_dist:
|
|
390
|
+
max_dist = dist
|
|
391
|
+
|
|
392
|
+
# Any region with at least one distance
|
|
393
|
+
# over the threshold is removed
|
|
394
|
+
if max_dist > 2.0:
|
|
395
|
+
for j in id2pos.values():
|
|
396
|
+
record = align[j]
|
|
397
|
+
record.letter_annotations['group'][i] = 0
|
|
398
|
+
|
|
399
|
+
# Remove conserved regions with less than 3 residues
|
|
400
|
+
ini = 0
|
|
401
|
+
end = align.get_alignment_length()
|
|
402
|
+
|
|
403
|
+
area = []
|
|
404
|
+
for i in range(ini, end):
|
|
405
|
+
s = set()
|
|
406
|
+
for position in id2pos.values():
|
|
407
|
+
record = align[position]
|
|
408
|
+
j = record.letter_annotations['group'][i]
|
|
409
|
+
if j == 1:
|
|
410
|
+
s = s.union({j})
|
|
411
|
+
if len(s) == 1:
|
|
412
|
+
area.append(1)
|
|
413
|
+
else:
|
|
414
|
+
area.append(0)
|
|
415
|
+
|
|
416
|
+
regions = []
|
|
417
|
+
regions_lengths = []
|
|
418
|
+
new_area = [0 for _ in area]
|
|
419
|
+
|
|
420
|
+
ini, end = 0, 0
|
|
421
|
+
while ini < len(area):
|
|
422
|
+
while ini < len(area):
|
|
423
|
+
if area[ini] == 1:
|
|
424
|
+
break
|
|
425
|
+
else:
|
|
426
|
+
ini += 1
|
|
427
|
+
|
|
428
|
+
end = ini
|
|
429
|
+
while end < len(area):
|
|
430
|
+
if area[end] != 1:
|
|
431
|
+
break
|
|
432
|
+
else:
|
|
433
|
+
end += 1
|
|
434
|
+
delta = end - ini
|
|
435
|
+
if 0 < delta < 3:
|
|
436
|
+
regions_lengths.append(delta)
|
|
437
|
+
regions.append((ini, end))
|
|
438
|
+
for i in range(ini, end):
|
|
439
|
+
new_area[i] = 1
|
|
440
|
+
ini = end
|
|
441
|
+
|
|
442
|
+
for region in regions:
|
|
443
|
+
ini, end = region
|
|
444
|
+
for i in range(ini, end):
|
|
445
|
+
for position in id2pos.values():
|
|
446
|
+
record = align[position]
|
|
447
|
+
record.letter_annotations['group'][i] = 0
|
|
448
|
+
|
|
449
|
+
# Remove non-conserved regions with less than 3 residues
|
|
450
|
+
ini = 0
|
|
451
|
+
end = align.get_alignment_length()
|
|
452
|
+
|
|
453
|
+
area = []
|
|
454
|
+
for i in range(ini, end):
|
|
455
|
+
s = set()
|
|
456
|
+
for position in id2pos.values():
|
|
457
|
+
record = align[position]
|
|
458
|
+
j = record.letter_annotations['group'][i]
|
|
459
|
+
if j == 0:
|
|
460
|
+
s = s.union({j})
|
|
461
|
+
if len(s) == 1:
|
|
462
|
+
area.append(1)
|
|
463
|
+
else:
|
|
464
|
+
area.append(0)
|
|
465
|
+
|
|
466
|
+
regions = []
|
|
467
|
+
regions_lengths = []
|
|
468
|
+
new_area = [0 for _ in area]
|
|
469
|
+
|
|
470
|
+
ini, end = 0, 0
|
|
471
|
+
while ini < len(area):
|
|
472
|
+
while ini < len(area):
|
|
473
|
+
if area[ini] == 1:
|
|
474
|
+
break
|
|
475
|
+
else:
|
|
476
|
+
ini += 1
|
|
477
|
+
|
|
478
|
+
end = ini
|
|
479
|
+
while end < len(area):
|
|
480
|
+
if area[end] != 1:
|
|
481
|
+
break
|
|
482
|
+
else:
|
|
483
|
+
end += 1
|
|
484
|
+
delta = end - ini
|
|
485
|
+
if 0 < delta < 3:
|
|
486
|
+
regions_lengths.append(delta)
|
|
487
|
+
regions.append((ini, end))
|
|
488
|
+
for i in range(ini, end):
|
|
489
|
+
new_area[i] = 1
|
|
490
|
+
ini = end
|
|
491
|
+
|
|
492
|
+
for region in regions:
|
|
493
|
+
ini, end = region
|
|
494
|
+
for i in range(ini, end):
|
|
495
|
+
for position in id2pos.values():
|
|
496
|
+
record = align[position]
|
|
497
|
+
record.letter_annotations['group'][i] = 1
|
|
498
|
+
|
|
499
|
+
# Output CSV coordinate files
|
|
500
|
+
if csv:
|
|
501
|
+
for idx in ca_coords.keys():
|
|
502
|
+
record = align[id2pos[idx]]
|
|
503
|
+
with open(f'{idx}.csv', 'w') as f:
|
|
504
|
+
for i, m in enumerate(ca_masked[idx]):
|
|
505
|
+
if m:
|
|
506
|
+
x = ca_coords[idx][i][0]
|
|
507
|
+
y = ca_coords[idx][i][1]
|
|
508
|
+
z = ca_coords[idx][i][2]
|
|
509
|
+
g = record.letter_annotations['group'][i]
|
|
510
|
+
f.write(f'{i},{x:.4f},{y:.4f},{z:.4f},{g}\n')
|
|
511
|
+
|
|
512
|
+
# Output superposed PDB files
|
|
513
|
+
if pdb:
|
|
514
|
+
io = PDBIO()
|
|
515
|
+
for idx in ca_coords.keys():
|
|
516
|
+
io.set_structure(structures[idx])
|
|
517
|
+
io.save(f'{idx}_sup.pdb')
|
|
518
|
+
|
|
519
|
+
return
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
def show_align(align, pal):
|
|
523
|
+
"""
|
|
524
|
+
Show the alignment colored by cluster using a Seaborn pallet
|
|
525
|
+
:param align: Alignment to show
|
|
526
|
+
:type align: Bio.Align.MultipleSeqAlignment
|
|
527
|
+
:param pal: Palette
|
|
528
|
+
:type pal: seaborn.palettes._ColorPalette
|
|
529
|
+
"""
|
|
530
|
+
length = align.get_alignment_length()
|
|
531
|
+
total = int(length / 50.0)
|
|
532
|
+
for j in range(total + 1):
|
|
533
|
+
ini = 0 + 50 * j
|
|
534
|
+
end = 49 + 50 * j + 1
|
|
535
|
+
|
|
536
|
+
if end >= length:
|
|
537
|
+
end = length - 1
|
|
538
|
+
|
|
539
|
+
if end > 100:
|
|
540
|
+
print(' ', end='')
|
|
541
|
+
for i in range(ini, end):
|
|
542
|
+
s = f'{i:03n}'
|
|
543
|
+
if i % 10 == 0:
|
|
544
|
+
print(s[0], end='')
|
|
545
|
+
else:
|
|
546
|
+
print(' ', end='')
|
|
547
|
+
print()
|
|
548
|
+
|
|
549
|
+
print(' ', end='')
|
|
550
|
+
for i in range(ini, end):
|
|
551
|
+
s = f'{i:03n}'
|
|
552
|
+
if i % 10 == 0:
|
|
553
|
+
print(s[1], end='')
|
|
554
|
+
else:
|
|
555
|
+
print(' ', end='')
|
|
556
|
+
print()
|
|
557
|
+
|
|
558
|
+
print(' ', end='')
|
|
559
|
+
for i in range(ini, end):
|
|
560
|
+
s = f'{i:03n}'
|
|
561
|
+
if i % 10 == 0:
|
|
562
|
+
print(s[2], end='')
|
|
563
|
+
else:
|
|
564
|
+
print(' ', end='')
|
|
565
|
+
print()
|
|
566
|
+
|
|
567
|
+
for record in align:
|
|
568
|
+
if 'structure' not in record.description:
|
|
569
|
+
continue
|
|
570
|
+
|
|
571
|
+
print(record.id, end=':')
|
|
572
|
+
for i in range(ini, end):
|
|
573
|
+
j = record.letter_annotations['group'][i] - 1
|
|
574
|
+
if j >= 0:
|
|
575
|
+
R = int(pal[j][0] * 255)
|
|
576
|
+
G = int(pal[j][1] * 255)
|
|
577
|
+
B = int(pal[j][2] * 255)
|
|
578
|
+
print(fg(R, G, B) + record.seq[i], end='')
|
|
579
|
+
else:
|
|
580
|
+
print(fg(125, 125, 125) + record.seq[i].lower(), end='')
|
|
581
|
+
print(fg.rs)
|
|
582
|
+
return
|