Oncodrive3D 1.0.4__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.
@@ -0,0 +1,97 @@
1
+ """
2
+ Module to get per-residue model confidence from all AlphaFold
3
+ predicted structures contained in a given directory.
4
+ """
5
+
6
+
7
+ import gzip
8
+ import os
9
+
10
+ import daiquiri
11
+ import pandas as pd
12
+ from Bio.Data.IUPACData import protein_letters_3to1
13
+ from Bio.PDB.PDBParser import PDBParser
14
+ from tqdm import tqdm
15
+
16
+ from scripts import __logger_name__
17
+ from scripts.datasets.utils import get_pdb_path_list_from_dir
18
+
19
+ logger = daiquiri.getLogger(__logger_name__ + ".build.model_confidence")
20
+
21
+
22
+ def get_3to1_protein_id(protein_id):
23
+ """
24
+ Convert a 3 letter protein code into 1 letter.
25
+ """
26
+ return protein_letters_3to1[protein_id.lower().capitalize()]
27
+
28
+
29
+ def get_confidence_one_chain(chain):
30
+ """
31
+ Get AF model confidence from its predicted structure.
32
+ """
33
+
34
+ res_ids = []
35
+ confidence_scores = []
36
+
37
+ # Iterate through the chain
38
+ for res in chain:
39
+ res_id = get_3to1_protein_id(res.resname)
40
+ confidence = res["CA"].get_bfactor()
41
+ res_ids.append(res_id)
42
+ confidence_scores.append(confidence)
43
+
44
+ return pd.DataFrame({"Res" : res_ids, "Confidence" : confidence_scores})
45
+
46
+
47
+ def get_confidence(input, output_dir, seq_df):
48
+ """
49
+ Get per-residue model confidence from all AlphaFold
50
+ predicted structures contained in a given directory.
51
+ """
52
+
53
+ checkpoint = os.path.join(output_dir, '.checkpoint.conf.txt')
54
+ if os.path.exists(checkpoint):
55
+ logger.debug("Confidence extraction performed: Skipping...")
56
+
57
+ else:
58
+ output = os.path.join(output_dir, 'confidence.tsv')
59
+
60
+ logger.debug(f"Input directory: {input}")
61
+ logger.debug(f"Output: {output}")
62
+
63
+ # Get model confidence
64
+ df_list = []
65
+ pdb_path_list = get_pdb_path_list_from_dir(input)
66
+
67
+ for file in tqdm(pdb_path_list, total=len(pdb_path_list), desc="Extracting model confidence"):
68
+ try:
69
+ identifier = file.split("AF-")[1].split("-model")[0].split("-F")
70
+ except Exception as e:
71
+ logger.warning(f'Could not extract Uniprot ID from {file}, {e}')
72
+
73
+ if identifier[0] in seq_df["Uniprot_ID"].values:
74
+
75
+ parser = PDBParser()
76
+ if file.endswith(".gz"):
77
+ with gzip.open(file, 'rt') as handle:
78
+ structure = parser.get_structure("ID", handle)
79
+ else:
80
+ with open(file, 'r') as handle:
81
+ structure = parser.get_structure("ID", handle)
82
+ chain = structure[0]["A"]
83
+
84
+ # Get confidence
85
+ confidence_df = get_confidence_one_chain(chain).reset_index().rename(columns={"index": "Pos"})
86
+ confidence_df["Pos"] = confidence_df["Pos"] + 1
87
+ confidence_df["Uniprot_ID"] = identifier[0]
88
+ confidence_df["AF_F"] = identifier[1]
89
+ df_list.append(confidence_df)
90
+
91
+ confidence_df = pd.concat(df_list).reset_index(drop=True)
92
+ confidence_df.to_csv(output, index=False, sep="\t")
93
+
94
+ with open(checkpoint, "w") as f:
95
+ f.write('')
96
+
97
+ logger.info("Extraction of model confidence completed!")
@@ -0,0 +1,64 @@
1
+ """
2
+ Simple module to convert the predicted aligned error (PAE)
3
+ files produced by AlphaFold2 from json to npy format.
4
+ """
5
+
6
+
7
+ import json
8
+ import os
9
+
10
+ import daiquiri
11
+ import numpy as np
12
+ from tqdm import tqdm
13
+
14
+ from scripts import __logger_name__
15
+
16
+ logger = daiquiri.getLogger(__logger_name__ + ".build.parse-pae")
17
+
18
+
19
+ def get_pae_path_list_from_dir(path_dir):
20
+ """
21
+ Takes as input the path of a directory and it
22
+ outputs a list of paths of the contained PAE files.
23
+ """
24
+
25
+ pae_files = os.listdir(path_dir)
26
+ pae_files = [os.path.join(path_dir, f) for f in pae_files if f.endswith('-predicted_aligned_error.json')]
27
+
28
+ return pae_files
29
+
30
+
31
+ def json_to_npy(path):
32
+ """
33
+ Convert file from .json to .npy
34
+ """
35
+
36
+ with open(path) as f:
37
+ pae = json.load(f)
38
+
39
+ return np.array(pae[0]['predicted_aligned_error'])
40
+
41
+
42
+ def parse_pae(input, output=None):
43
+ """
44
+ Convert all PAE files in the input directory from .json to .npy.
45
+ """
46
+
47
+ if output is None:
48
+ output = input
49
+
50
+ checkpoint = os.path.join(output, '.checkpoint.parse.txt')
51
+ if os.path.exists(checkpoint):
52
+ logger.debug("PAE already parsed: Skipping...")
53
+
54
+ else:
55
+ if not os.path.exists(output):
56
+ os.makedirs(output)
57
+
58
+ path_files = get_pae_path_list_from_dir(input)
59
+
60
+ for path in tqdm(path_files, total=len(path_files), desc="Parsing PAE"):
61
+ np.save(path.replace(".json", ".npy"), json_to_npy(path))
62
+
63
+ with open(checkpoint, "w") as f:
64
+ f.write('')
@@ -0,0 +1,258 @@
1
+ """
2
+ Module to compute maps of probabilities of contact (pCMAPs) from
3
+ AlphaFold predicted structures and predicted aligned error (PAE).
4
+
5
+ The pCMAPs is a dataframe including the probability of contact for
6
+ each pair of residue of a given protein. Given a threshold (10Å as
7
+ default) to define the contact, the probability that two residues i
8
+ and j are in contact is computed considering the distance between i
9
+ and j in the PDB structure and their predicted error in the PAE.
10
+ """
11
+
12
+
13
+ import gzip
14
+ import daiquiri
15
+ import multiprocessing
16
+ import os
17
+ import re
18
+ from math import pi
19
+
20
+ import numpy as np
21
+ from Bio.Data.IUPACData import protein_letters_3to1
22
+ from Bio.PDB.PDBParser import PDBParser
23
+
24
+ from scripts import __logger_name__
25
+ from scripts.datasets.utils import (get_af_id_from_pdb,
26
+ get_pdb_path_list_from_dir)
27
+
28
+ logger = daiquiri.getLogger(__logger_name__ + ".build.prob_contact_maps")
29
+
30
+
31
+ # Functions to compute the probability of contact
32
+
33
+ def cap(r, h):
34
+ """
35
+ Volume of the polar cap of the sphere with radius r and cap height h
36
+ """
37
+ return pi * (3 * r - h) * h ** 2 / 3
38
+
39
+
40
+ def vol(r):
41
+ """
42
+ Volume of sphere with radius r
43
+ """
44
+ return 4 * pi * r ** 3 / 3
45
+
46
+
47
+ def s2_minus_s1(r1, r2, d):
48
+
49
+ """
50
+ Volume of S2 outside of S1
51
+ r1: radius of S1
52
+ r2: radius of S2
53
+ d: distance between center of S1 and center of S2
54
+ """
55
+
56
+ # S1 and S2 not in contact
57
+ if d > r1 + r2:
58
+ return vol(r2)
59
+
60
+ # S1 sits inside S2
61
+ elif (r2 > d + r1):
62
+ return vol(r2) - vol(r1)
63
+
64
+ # S2 sits inside S1
65
+ elif (r1 > d + r2):
66
+ return 0.
67
+
68
+ # Center of S2 is outside S1
69
+ elif d > r1:
70
+ h1 = (r2 ** 2 - (d - r1) ** 2) / (2 * d)
71
+ h2 = r1 - h1 + r2 - d
72
+ return vol(r2) - cap(r1, h1) - cap(r2, h2)
73
+
74
+ # Center of S2 is inside S1
75
+ elif d <= r1:
76
+ h1 = (r2 ** 2 - (r1 - d) ** 2) / (2 * d)
77
+ h2 = d - r1 + h1 + r2
78
+ return cap(r2, h2) - cap(r1, h1)
79
+
80
+
81
+ # Other functions
82
+
83
+ def get_structure(file):
84
+ """
85
+ Use Bio.PDB to parse protein structure.
86
+ """
87
+
88
+ id = file.split("AF-")[1].split("-model_v1")[0]
89
+
90
+ if file.endswith('.gz'):
91
+ with gzip.open(file, 'rt') as handle:
92
+ return PDBParser().get_structure(id=id, file=handle)[0]
93
+ else:
94
+ with open(file, 'r') as handle:
95
+ return PDBParser().get_structure(id=id, file=handle)[0]
96
+
97
+
98
+ def get_3to1_protein_id(protein_id):
99
+ """
100
+ Convert a 3 letter protein code into 1 letter.
101
+ """
102
+
103
+ return protein_letters_3to1[protein_id.lower().capitalize()]
104
+
105
+
106
+ def get_dist_matrix(chain) :
107
+ """
108
+ Compute the distance matrix between C-alpha of a protein.
109
+ """
110
+
111
+ m = np.zeros((len(chain), len(chain)), float)
112
+
113
+ for i, res1 in enumerate(chain) :
114
+ for j, res2 in enumerate(chain) :
115
+ m[i, j] = abs(res1["CA"] - res2["CA"])
116
+
117
+ return m
118
+
119
+
120
+ def get_contact_map(chain, distance=10):
121
+ """
122
+ Compute the contact map between C-alpha of a protein.
123
+ """
124
+
125
+ dist_matrix = get_dist_matrix(chain)
126
+
127
+ return dist_matrix < distance
128
+
129
+
130
+ def get_prob_contact(pae_value, dmap_value, distance=10):
131
+ """
132
+ Get probability of contact considering the distance
133
+ between residues in the predicted structure and the
134
+ Predicted Aligned Error (PAE).
135
+ """
136
+
137
+ if pae_value == 0:
138
+
139
+ if dmap_value < distance:
140
+ return 1
141
+ else:
142
+ return 0
143
+
144
+ else:
145
+ # Get the volume of res2 outside of res1
146
+ vol_s2_out_s1 = s2_minus_s1(r1=distance, r2=pae_value, d=dmap_value)
147
+
148
+ # Get the probability that s2 is out of s1
149
+ p_s2_in_s1 = vol_s2_out_s1 / vol(pae_value)
150
+
151
+ return 1 - p_s2_in_s1
152
+
153
+
154
+ def get_prob_cmap(chain, pae, distance=10) :
155
+ """
156
+ Compute the probabilities that each pair of residue in a protein are
157
+ in contact taking into account the Predicted Aligned Error (PAE) and
158
+ the PDB structure predicted by AlphaFold 2
159
+ """
160
+
161
+ m = np.zeros((len(chain), len(chain)), float)
162
+
163
+ for i, res1 in enumerate(chain):
164
+ for j, res2 in enumerate(chain):
165
+ d = abs(res1["CA"] - res2["CA"])
166
+ m[i, j] = get_prob_contact(pae[i, j], d, distance)
167
+
168
+ return m
169
+
170
+
171
+ def get_prob_cmaps(pdb_files, pae_path, output_path, distance=10, num_process=0):
172
+ """
173
+ Given a list of path of PDB file, compute the probabilistic cmap of
174
+ each PDB non-fragmented structure and save it as individual .npy file
175
+ in the given output path. For fragmented structures simply get cmaps.
176
+ """
177
+
178
+ # Iterate through the files and save probabilsitic cmap
179
+ for n, file in enumerate(pdb_files):
180
+ identifier = get_af_id_from_pdb(file)
181
+
182
+ # Get fragmented number
183
+ af_f = identifier.split("-F")[1]
184
+ if af_f.isnumeric():
185
+ af_f = int(af_f)
186
+ else:
187
+ af_f = int(re.sub(r"\D", "", af_f))
188
+
189
+ # Get CMAP for fragmented structures (PAE not available yet)
190
+ if af_f > 1:
191
+ try:
192
+ cmap = get_contact_map(get_structure(file)["A"], distance=distance)
193
+ np.save(os.path.join(output_path, f"{identifier}.npy"), cmap)
194
+ except Exception as e:
195
+ logger.warning(f"Could not process {identifier}")
196
+ logger.warning(f"Error: {e}")
197
+ with open(os.path.join(output_path, "ids_not_processed.txt"), 'a+') as file:
198
+ file.write(identifier + '\n')
199
+
200
+ # Get probabilistic CMAP
201
+ else:
202
+ try:
203
+ pae = np.load(os.path.join(pae_path, f"{identifier}-predicted_aligned_error.npy"))
204
+ chain = get_structure(file)["A"]
205
+ prob_cmap = get_prob_cmap(chain, pae, distance=distance)
206
+ np.save(os.path.join(output_path, f"{identifier}.npy"), prob_cmap)
207
+ except Exception as e:
208
+ logger.warning(f"Could not process {identifier}")
209
+ logger.warning(f"Error: {e}")
210
+ with open(os.path.join(output_path, "ids_not_processed.txt"), 'a+') as file:
211
+ file.write(identifier + '\n')
212
+
213
+ # Monitor processing
214
+ if n % 100 == 0:
215
+ if n == 0:
216
+ logger.debug(f"Process [{num_process}] starting processing [{len(pdb_files)}] structures...")
217
+ else:
218
+ logger.debug(f"Process [{num_process}] completed [{n}/{len(pdb_files)}] structures...")
219
+ elif n+1 == len(pdb_files):
220
+ logger.debug(f"Process [{num_process}] completed!")
221
+
222
+
223
+
224
+ def get_prob_cmaps_mp(input_pdb,
225
+ input_pae,
226
+ output,
227
+ distance = 10,
228
+ num_cores = 1):
229
+ """
230
+ Given a list of path of PDB file, use multiprocessing to compute pCMAPs
231
+ (maps or probabilities of contact between each residues) for each PDB
232
+ non-fragmented structure and save it as individual .npy file in the given
233
+ output path. For fragmented structures simply get cmaps.
234
+ """
235
+
236
+ n_structures = len([pdb for pdb in os.listdir(input_pdb) if ".pdb" in pdb])
237
+ logger.debug(f"Input PDB directory: {input_pdb}")
238
+ logger.debug(f"Input PAE directory: {input_pae}")
239
+ logger.debug(f"Output: {output}")
240
+ logger.debug(f"Distance: {distance}")
241
+ logger.debug(f"Cores: {num_cores}")
242
+
243
+ # Create necessary folder
244
+ if not os.path.exists(output):
245
+ os.makedirs(output)
246
+
247
+ # Get the path of all pdb files in the directorys
248
+ pdb_path_lst = get_pdb_path_list_from_dir(input_pdb)
249
+
250
+ # Split the PDB files into chunks for each process
251
+ chunk_size = int(len(pdb_path_lst) / num_cores) + 1
252
+ chunks = [pdb_path_lst[i : i + chunk_size] for i in range(0, len(pdb_path_lst), chunk_size)]
253
+
254
+ # Create a pool of processes and compute the cmaps in parallel
255
+ logger.debug(f"Processing [{n_structures}] structures by [{len(chunks)}] processes...")
256
+ with multiprocessing.Pool(processes = num_cores) as pool:
257
+ results = pool.starmap(get_prob_cmaps,
258
+ [(chunk, input_pae, output, distance, n) for n, chunk in enumerate(chunks)])