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.
- oncodrive3d-1.0.4.dist-info/METADATA +333 -0
- oncodrive3d-1.0.4.dist-info/RECORD +36 -0
- oncodrive3d-1.0.4.dist-info/WHEEL +4 -0
- oncodrive3d-1.0.4.dist-info/entry_points.txt +5 -0
- oncodrive3d-1.0.4.dist-info/licenses/LICENSE +15 -0
- scripts/__init__.py +2 -0
- scripts/clustering_3d.code-workspace +7 -0
- scripts/datasets/__init__.py +0 -0
- scripts/datasets/af_merge.py +344 -0
- scripts/datasets/build_datasets.py +125 -0
- scripts/datasets/get_pae.py +78 -0
- scripts/datasets/get_structures.py +107 -0
- scripts/datasets/model_confidence.py +97 -0
- scripts/datasets/parse_pae.py +64 -0
- scripts/datasets/prob_contact_maps.py +258 -0
- scripts/datasets/seq_for_mut_prob.py +900 -0
- scripts/datasets/utils.py +394 -0
- scripts/globals.py +169 -0
- scripts/main.py +650 -0
- scripts/plotting/__init__.py +0 -0
- scripts/plotting/build_annotations.py +102 -0
- scripts/plotting/chimerax_plot.py +251 -0
- scripts/plotting/pdb_tool.py +149 -0
- scripts/plotting/pfam.py +94 -0
- scripts/plotting/plot.py +2484 -0
- scripts/plotting/stability_change.py +184 -0
- scripts/plotting/uniprot_feat.py +276 -0
- scripts/plotting/utils.py +594 -0
- scripts/run/__init__.py +0 -0
- scripts/run/clustering.py +749 -0
- scripts/run/communities.py +53 -0
- scripts/run/miss_mut_prob.py +206 -0
- scripts/run/mutability.py +289 -0
- scripts/run/pvalues.py +91 -0
- scripts/run/score_and_simulations.py +155 -0
- scripts/run/utils.py +461 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import pandas as pd
|
|
4
|
+
|
|
5
|
+
import daiquiri
|
|
6
|
+
|
|
7
|
+
from scripts import __logger_name__
|
|
8
|
+
from scripts.globals import copy_dir
|
|
9
|
+
from scripts.plotting.utils import clean_annot_dir
|
|
10
|
+
from scripts.plotting.utils import get_species
|
|
11
|
+
from scripts.plotting.stability_change import download_stability_change, parse_ddg_rasp
|
|
12
|
+
from scripts.plotting.pdb_tool import run_pdb_tool, parse_pdb_tool
|
|
13
|
+
from scripts.plotting.pfam import get_pfam
|
|
14
|
+
from scripts.plotting.uniprot_feat import get_uniprot_feat
|
|
15
|
+
|
|
16
|
+
logger = daiquiri.getLogger(__logger_name__ + ".plotting.build")
|
|
17
|
+
|
|
18
|
+
logging.getLogger('urllib3.connectionpool').setLevel(logging.WARNING)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# TODO: fix multiprocessing on DDG
|
|
23
|
+
# TODO: multiprocessing on pdb_tool
|
|
24
|
+
|
|
25
|
+
# =================
|
|
26
|
+
# BUILD ANNOTATIONS
|
|
27
|
+
# =================
|
|
28
|
+
|
|
29
|
+
def get_annotations(data_dir,
|
|
30
|
+
output_dir,
|
|
31
|
+
ddg_dir,
|
|
32
|
+
#path_pdb_tool_sif,
|
|
33
|
+
organism,
|
|
34
|
+
cores):
|
|
35
|
+
"""
|
|
36
|
+
Main function to build annotations to generate annotated plots.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
# Empty directory and load sequence df
|
|
40
|
+
clean_annot_dir(output_dir, 'd')
|
|
41
|
+
|
|
42
|
+
# # Get ddG
|
|
43
|
+
species = get_species(organism)
|
|
44
|
+
logger.info(f"Obtaining stability change..")
|
|
45
|
+
if species == "Homo sapiens" or species == "Mus musculus":
|
|
46
|
+
ddg_output = os.path.join(output_dir, "stability_change")
|
|
47
|
+
os.makedirs(ddg_output, exist_ok=True)
|
|
48
|
+
if ddg_dir is not None:
|
|
49
|
+
# Copy ddG from path
|
|
50
|
+
temp_ddg_path = os.path.join(output_dir, "stability_change_temp")
|
|
51
|
+
copy_dir(source_dir=ddg_dir, destination_dir=temp_ddg_path)
|
|
52
|
+
else:
|
|
53
|
+
# Download ddG
|
|
54
|
+
temp_ddg_path = download_stability_change(ddg_output, cores)
|
|
55
|
+
logger.info(f"Completed!")
|
|
56
|
+
|
|
57
|
+
## TODO: Optimize DDG parsing
|
|
58
|
+
## - only one protein is allocated to one process every time
|
|
59
|
+
## - a list of proteins should be allocated instead
|
|
60
|
+
|
|
61
|
+
# Parsing DDG
|
|
62
|
+
logger.info(f"Parsing stability change..")
|
|
63
|
+
parse_ddg_rasp(temp_ddg_path, ddg_output, cores)
|
|
64
|
+
logger.info(f"Parsing completed!")
|
|
65
|
+
else:
|
|
66
|
+
logger.warning(f"Currently, stability change annotation is not available for {species} but only for Homo sapiens: Skipping...")
|
|
67
|
+
|
|
68
|
+
## TODO: Enable multiprocessing for PDB_Tool
|
|
69
|
+
## TODO: Evaluate the possibility of installing PDB_Tool instead of using container
|
|
70
|
+
|
|
71
|
+
# Run PDB_Tool
|
|
72
|
+
logger.info(f"Extracting pACC and 2° structure..")
|
|
73
|
+
path_pdb_structure = os.path.join(data_dir, "pdb_structures")
|
|
74
|
+
pdb_tool_output = run_pdb_tool(input_dir=path_pdb_structure, output_dir=output_dir)
|
|
75
|
+
logger.info(f"Extraction completed!")
|
|
76
|
+
|
|
77
|
+
# Parse PDB_Tool
|
|
78
|
+
logger.info(f"Parsing pACC and 2° structures..")
|
|
79
|
+
parse_pdb_tool(input_dir=pdb_tool_output, output_dir=output_dir)
|
|
80
|
+
logger.info(f"Parsing completed!")
|
|
81
|
+
|
|
82
|
+
# Get Pfam annotations
|
|
83
|
+
logger.info(f"Downloading and parsing Pfam..")
|
|
84
|
+
seq_df = pd.read_table(os.path.join(data_dir, "seq_for_mut_prob.tsv"))
|
|
85
|
+
pfam_df = get_pfam(seq_df = seq_df,
|
|
86
|
+
output_tsv = os.path.join(output_dir, "pfam.tsv"),
|
|
87
|
+
organism = species)
|
|
88
|
+
logger.info(f"Completed!")
|
|
89
|
+
|
|
90
|
+
# Get Uniprot features
|
|
91
|
+
logger.info(f"Downloading and parsing Features from Uniprot..")
|
|
92
|
+
get_uniprot_feat(seq_df = seq_df,
|
|
93
|
+
pfam_df = pfam_df,
|
|
94
|
+
output_tsv = os.path.join(output_dir, "uniprot_feat.tsv"))
|
|
95
|
+
logger.info(f"Completed!")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
if __name__ == "__main__":
|
|
99
|
+
get_annotations(data_dir = "/workspace/nobackup/scratch/oncodrive3d/datasets",
|
|
100
|
+
output_dir = "/workspace/nobackup/scratch/oncodrive3d/annotations",
|
|
101
|
+
organism = "Homo sapiens",
|
|
102
|
+
cores = 4)
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Use ChimeraX to generate 3D structures colored by metrics
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
import numpy as np
|
|
7
|
+
import os
|
|
8
|
+
import subprocess
|
|
9
|
+
import math
|
|
10
|
+
import daiquiri
|
|
11
|
+
|
|
12
|
+
from scripts import __logger_name__
|
|
13
|
+
logger = daiquiri.getLogger(__logger_name__ + ".plotting.chimerax_plot")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def create_attribute_file(path_to_file,
|
|
17
|
+
df,
|
|
18
|
+
attribute_col,
|
|
19
|
+
pos_col="Pos",
|
|
20
|
+
attribute_name="local_attribute",
|
|
21
|
+
gene="Gene_name",
|
|
22
|
+
protein="Protein_name"):
|
|
23
|
+
|
|
24
|
+
logger.info(f"Saving {path_to_file}")
|
|
25
|
+
with open(path_to_file, 'w') as f:
|
|
26
|
+
f.write("#\n")
|
|
27
|
+
f.write(f"# Mutations in volume for {protein} ({gene})\n")
|
|
28
|
+
f.write("#\n")
|
|
29
|
+
f.write("# Use this file to assign the attribute in Chimera with the \n")
|
|
30
|
+
f.write("# Define Attribute tool or the command defattr.\n")
|
|
31
|
+
f.write("#\n")
|
|
32
|
+
f.write(f"attribute: {attribute_name}\n")
|
|
33
|
+
f.write("recipient: residues\n")
|
|
34
|
+
|
|
35
|
+
for _, row in df.iterrows():
|
|
36
|
+
f.write(f"\t:{int(row[pos_col])}\t{row[attribute_col]}\n")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def round_to_first_nonzero(num):
|
|
40
|
+
if num == 0:
|
|
41
|
+
return 0
|
|
42
|
+
|
|
43
|
+
scale = -int(math.floor(math.log10(abs(num))))
|
|
44
|
+
shifted_num = num * (10 ** scale)
|
|
45
|
+
rounded_num = round(shifted_num)
|
|
46
|
+
result = rounded_num / (10 ** scale)
|
|
47
|
+
|
|
48
|
+
return result
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def is_float_an_integer(value):
|
|
52
|
+
if isinstance(value, float):
|
|
53
|
+
return value.is_integer()
|
|
54
|
+
return False
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_intervals(attribute_vector, attribute):
|
|
58
|
+
|
|
59
|
+
max_value = attribute_vector.max()
|
|
60
|
+
min_value = attribute_vector.min()
|
|
61
|
+
min_value = 0 if attribute == "score" else 1
|
|
62
|
+
intervals = np.linspace(min_value, max_value, 5)
|
|
63
|
+
intervals = np.round(intervals, 2) if attribute == "score" else np.round(intervals)
|
|
64
|
+
if attribute == "logscore":
|
|
65
|
+
pos_values = np.linspace(0, max_value, 3)
|
|
66
|
+
intervals = np.round([-max_value, -pos_values[1], 0, pos_values[1], max_value], 2)
|
|
67
|
+
intervals = [round(n) if is_float_an_integer(n) else n for n in intervals]
|
|
68
|
+
|
|
69
|
+
return intervals
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def get_palette(intervals, type="diverging"):
|
|
73
|
+
|
|
74
|
+
# Diverging palette
|
|
75
|
+
if type == "diverging":
|
|
76
|
+
return f"{intervals[0]},#0571B0:{intervals[1]},#92C5DE:{intervals[2]},white:{intervals[3]},#F4A582:{intervals[4]},#CA0020"
|
|
77
|
+
|
|
78
|
+
# Sequential palette
|
|
79
|
+
else:
|
|
80
|
+
return f"{intervals[0]},#FFFFB2:{intervals[1]},#FECC5C:{intervals[2]},#FD8D3C:{intervals[3]},#F03B20:{intervals[4]},#BD0026"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def get_chimerax_command(chimerax_bin,
|
|
84
|
+
pdb_path,
|
|
85
|
+
chimera_output_path,
|
|
86
|
+
attr_file_path,
|
|
87
|
+
attribute,
|
|
88
|
+
intervals,
|
|
89
|
+
gene,
|
|
90
|
+
uni_id,
|
|
91
|
+
labels,
|
|
92
|
+
i,
|
|
93
|
+
f,
|
|
94
|
+
cohort="",
|
|
95
|
+
clusters=None,
|
|
96
|
+
pixelsize=0.1,
|
|
97
|
+
transparent_bg=False):
|
|
98
|
+
|
|
99
|
+
palette = get_palette(intervals, type="diverging") if attribute == "logscore" else get_palette(intervals, type="sequential")
|
|
100
|
+
transparent_bg = " transparentBackground true" if transparent_bg else ""
|
|
101
|
+
|
|
102
|
+
chimerax_command = (
|
|
103
|
+
f"{chimerax_bin} --nogui --offscreen --silent --cmd "
|
|
104
|
+
f"\"open {pdb_path}; "
|
|
105
|
+
"set bgColor white; "
|
|
106
|
+
"color lightgray; "
|
|
107
|
+
f"open {attr_file_path}; "
|
|
108
|
+
f"color byattribute {attribute} palette {palette}; "
|
|
109
|
+
f"key {palette} :{intervals[0]} :{intervals[1]} :{intervals[2]} :{intervals[3]} :{intervals[4]} pos 0.35,0.03 fontSize 4 size 0.3,0.02;"
|
|
110
|
+
f"2dlabels create label text '{labels[attribute]}' size 6 color darkred xpos 0.34 ypos 0.065;"
|
|
111
|
+
f"2dlabels create title text '{gene} - {uni_id}-F{f} ' size 6 color darkred xpos 0.35 ypos 0.93;"
|
|
112
|
+
"hide atoms;"
|
|
113
|
+
"show cartoons;"
|
|
114
|
+
"lighting soft;"
|
|
115
|
+
"graphics silhouettes true width 1.3;"
|
|
116
|
+
"zoom;"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
if clusters is not None and len(clusters) > 0:
|
|
120
|
+
for pos in clusters:
|
|
121
|
+
chimerax_command += f"marker #10 position :{pos} color #dacae961 radius 5.919;"
|
|
122
|
+
cluster_tag = "_clusters"
|
|
123
|
+
else:
|
|
124
|
+
cluster_tag = ""
|
|
125
|
+
|
|
126
|
+
output_path = os.path.join(chimera_output_path, f"{cohort}_{i}_{gene}_{attribute}{cluster_tag}.png")
|
|
127
|
+
chimerax_command += f"save {output_path} pixelSize {pixelsize} supersample 3{transparent_bg};exit\""
|
|
128
|
+
|
|
129
|
+
return chimerax_command
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def generate_chimerax_plot(output_dir,
|
|
133
|
+
gene_result_path,
|
|
134
|
+
pos_result_path,
|
|
135
|
+
datasets_dir,
|
|
136
|
+
seq_df_path,
|
|
137
|
+
cohort,
|
|
138
|
+
max_genes,
|
|
139
|
+
pixel_size,
|
|
140
|
+
cluster_ext,
|
|
141
|
+
fragmented_proteins,
|
|
142
|
+
transparent_bg,
|
|
143
|
+
chimerax_bin):
|
|
144
|
+
|
|
145
|
+
seq_df = pd.read_csv(seq_df_path, sep="\t")
|
|
146
|
+
gene_result = pd.read_csv(gene_result_path)
|
|
147
|
+
result = pd.read_csv(pos_result_path)
|
|
148
|
+
if "Ratio_obs_sim" in result.columns:
|
|
149
|
+
result = result.rename(columns={"Ratio_obs_sim" : "Score_obs_sim"})
|
|
150
|
+
result["Logscore_obs_sim"] = np.log(result["Score_obs_sim"])
|
|
151
|
+
|
|
152
|
+
# Process each gene
|
|
153
|
+
genes = gene_result[gene_result["C_gene"] == 1].Gene.unique()
|
|
154
|
+
if len(genes) > 0:
|
|
155
|
+
|
|
156
|
+
chimera_out_path = os.path.join(output_dir, f"{cohort}.chimerax")
|
|
157
|
+
chimera_attr_path = os.path.join(chimera_out_path, "attributes")
|
|
158
|
+
chimera_plots_path = os.path.join(chimera_out_path, "plots")
|
|
159
|
+
for path in [chimera_out_path, chimera_attr_path, chimera_plots_path]:
|
|
160
|
+
os.makedirs(path, exist_ok=True)
|
|
161
|
+
|
|
162
|
+
for i, gene in enumerate(genes[:max_genes]):
|
|
163
|
+
logger.info(f"Processing {gene}")
|
|
164
|
+
|
|
165
|
+
# Attribute files
|
|
166
|
+
logger.debug("Preprocessing for attribute files..")
|
|
167
|
+
result_gene = result[result["Gene"] == gene]
|
|
168
|
+
if cluster_ext:
|
|
169
|
+
clusters = result_gene[result_gene.C == 1].Pos.values
|
|
170
|
+
else:
|
|
171
|
+
clusters = result_gene[(result_gene.C == 1) & (result_gene.C_ext == 0)].Pos.values
|
|
172
|
+
len_gene = len(seq_df[seq_df["Gene"] == gene].Seq.values[0])
|
|
173
|
+
result_gene = pd.DataFrame({"Pos" : range(1, len_gene+1)}).merge(
|
|
174
|
+
result_gene[["Pos",
|
|
175
|
+
"Mut_in_res",
|
|
176
|
+
"Mut_in_vol",
|
|
177
|
+
"Score_obs_sim",
|
|
178
|
+
"Logscore_obs_sim"]], on="Pos", how="left")
|
|
179
|
+
|
|
180
|
+
uni_id, f = seq_df[seq_df["Gene"] == gene][["Uniprot_ID", "F"]].values[0]
|
|
181
|
+
pdb_path = os.path.join(datasets_dir, "pdb_structures", f"AF-{uni_id}-F{f}-model_v4.pdb")
|
|
182
|
+
|
|
183
|
+
labels = {"mutres" : "Mutations in residue ",
|
|
184
|
+
"mutvol" : "Mutations in volume ",
|
|
185
|
+
"score" : " Clustering score ",
|
|
186
|
+
"logscore" : "log(Clustering score) "}
|
|
187
|
+
cols = {"mutres" : "Mut_in_res",
|
|
188
|
+
"mutvol" : "Mut_in_vol",
|
|
189
|
+
"score" : "Score_obs_sim",
|
|
190
|
+
"logscore" : "Logscore_obs_sim"}
|
|
191
|
+
|
|
192
|
+
if fragmented_proteins == False:
|
|
193
|
+
if f != 1:
|
|
194
|
+
logger.debug(f"Fragmented protein processing {fragmented_proteins}: Skipping {gene} ({uni_id}-F{f})..")
|
|
195
|
+
continue
|
|
196
|
+
|
|
197
|
+
if os.path.exists(pdb_path):
|
|
198
|
+
|
|
199
|
+
for attribute in ["mutres", "mutvol", "score", "logscore"]:
|
|
200
|
+
|
|
201
|
+
logger.debug("Generating attribute files..")
|
|
202
|
+
attr_file_path = f"{chimera_attr_path}/{gene}_{attribute}.defattr"
|
|
203
|
+
create_attribute_file(path_to_file=attr_file_path,
|
|
204
|
+
df=result_gene.dropna(),
|
|
205
|
+
attribute_col=cols[attribute],
|
|
206
|
+
attribute_name=attribute)
|
|
207
|
+
|
|
208
|
+
attribute_vector = result_gene[cols[attribute]]
|
|
209
|
+
intervals = get_intervals(attribute_vector, attribute)
|
|
210
|
+
|
|
211
|
+
logger.debug("Generating 3D images..")
|
|
212
|
+
chimerax_command = get_chimerax_command(chimerax_bin,
|
|
213
|
+
pdb_path,
|
|
214
|
+
chimera_plots_path,
|
|
215
|
+
attr_file_path,
|
|
216
|
+
attribute,
|
|
217
|
+
intervals,
|
|
218
|
+
gene,
|
|
219
|
+
uni_id,
|
|
220
|
+
labels,
|
|
221
|
+
i,
|
|
222
|
+
f,
|
|
223
|
+
cohort,
|
|
224
|
+
pixelsize=pixel_size,
|
|
225
|
+
transparent_bg=transparent_bg)
|
|
226
|
+
subprocess.run(chimerax_command, shell=True)
|
|
227
|
+
logger.debug(chimerax_command)
|
|
228
|
+
|
|
229
|
+
if attribute == "score" or attribute == "logscore":
|
|
230
|
+
chimerax_command = get_chimerax_command(chimerax_bin,
|
|
231
|
+
pdb_path,
|
|
232
|
+
chimera_plots_path,
|
|
233
|
+
attr_file_path,
|
|
234
|
+
attribute,
|
|
235
|
+
intervals,
|
|
236
|
+
gene,
|
|
237
|
+
uni_id,
|
|
238
|
+
labels,
|
|
239
|
+
i,
|
|
240
|
+
f,
|
|
241
|
+
cohort,
|
|
242
|
+
clusters=clusters,
|
|
243
|
+
pixelsize=pixel_size,
|
|
244
|
+
transparent_bg=transparent_bg)
|
|
245
|
+
subprocess.run(chimerax_command, shell=True)
|
|
246
|
+
logger.debug(chimerax_command)
|
|
247
|
+
|
|
248
|
+
else:
|
|
249
|
+
logger.warning(f"PDB path missing: {pdb_path}")
|
|
250
|
+
else:
|
|
251
|
+
logger.info("Nothing to plot!")
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
import daiquiri
|
|
5
|
+
import subprocess
|
|
6
|
+
import pandas as pd
|
|
7
|
+
import numpy as np
|
|
8
|
+
import re
|
|
9
|
+
import shutil
|
|
10
|
+
from tqdm import tqdm
|
|
11
|
+
|
|
12
|
+
from scripts import __logger_name__
|
|
13
|
+
|
|
14
|
+
logger = daiquiri.getLogger(__logger_name__ + ".plotting.pdb_tool")
|
|
15
|
+
|
|
16
|
+
logging.getLogger('urllib3.connectionpool').setLevel(logging.WARNING)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# ========
|
|
21
|
+
# PDB_Tool
|
|
22
|
+
# ========
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def decompress_pdb_gz(input_dir):
|
|
26
|
+
"""
|
|
27
|
+
Extract all .gz PDB files in directory.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
files_in_directory = os.listdir(input_dir)
|
|
31
|
+
gz_file_present = any(file.endswith(".pdb.gz") for file in files_in_directory)
|
|
32
|
+
if gz_file_present:
|
|
33
|
+
logger.debug("Decompressing .gz PDB files...")
|
|
34
|
+
command = f'gunzip -q {os.path.join(input_dir, "*.pdb.gz")}'
|
|
35
|
+
subprocess.run(command, shell=True)
|
|
36
|
+
logger.debug("Decompression complete!")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def run_pdb_tool(input_dir, output_dir, f="4"):
|
|
40
|
+
"""
|
|
41
|
+
Use PDB_Tool to extract features from all pdb files in directory.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
pdb_tool_output = os.path.join(output_dir, "pdb_tool")
|
|
45
|
+
if not os.path.isdir(pdb_tool_output):
|
|
46
|
+
os.makedirs(pdb_tool_output)
|
|
47
|
+
logger.debug(f'mkdir {pdb_tool_output}')
|
|
48
|
+
|
|
49
|
+
decompress_pdb_gz(input_dir)
|
|
50
|
+
pdb_files = [file for file in os.listdir(input_dir) if file.endswith(".pdb")]
|
|
51
|
+
logger.debug("Running PDB_Tool...")
|
|
52
|
+
for file in tqdm(pdb_files, desc="Running PDB_Tool"):
|
|
53
|
+
output = os.path.join(pdb_tool_output, file.replace(".pdb", ".feature"))
|
|
54
|
+
# Singularity container
|
|
55
|
+
#subprocess.run(["singularity", "exec", f"{pdb_tool_sif_path}", "/PDB_Tool/PDB_Tool", "-i", f"{input_dir}/{file}", "-o", output, "-F", f])
|
|
56
|
+
# Added to $PATH
|
|
57
|
+
subprocess.run(["PDB_Tool", "-i", os.path.join(input_dir, file), "-o", output, "-F", f])
|
|
58
|
+
|
|
59
|
+
return pdb_tool_output
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def load_pdb_tool_file(path):
|
|
63
|
+
"""
|
|
64
|
+
Parse .feature file obtained by PDB_Tool.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
with open(path, "r") as f:
|
|
68
|
+
lines = f.readlines()
|
|
69
|
+
lst_lines = []
|
|
70
|
+
for l in lines:
|
|
71
|
+
lst_lines.append(l.strip().split())
|
|
72
|
+
# return lst_lines
|
|
73
|
+
df = pd.DataFrame(lst_lines[1:], columns = lst_lines[0]).iloc[:,:9]
|
|
74
|
+
df = df.drop("Missing", axis=1)
|
|
75
|
+
df = df.rename(columns={"Num" : "Pos"})
|
|
76
|
+
|
|
77
|
+
for c in df.columns:
|
|
78
|
+
if c == "Pos" or c == "ACC" or c == "CNa" or c == "CNb":
|
|
79
|
+
data_type = int
|
|
80
|
+
else:
|
|
81
|
+
data_type = float
|
|
82
|
+
try:
|
|
83
|
+
df[c] = df[c].astype(data_type)
|
|
84
|
+
except:
|
|
85
|
+
pass
|
|
86
|
+
return df
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def get_pdb_tool_file_in_dir(path):
|
|
90
|
+
"""
|
|
91
|
+
Get the list of PDB_Tool .feature files from directory.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
list_files = os.listdir(path)
|
|
95
|
+
ix = [re.search('.\.feature$', x) is not None for x in list_files]
|
|
96
|
+
|
|
97
|
+
return list(np.array(list_files)[np.array(ix)])
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def load_all_pdb_tool_files(path):
|
|
101
|
+
"""
|
|
102
|
+
Get the features of all proteins in the directory.
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
df_list = []
|
|
106
|
+
feature_file_list = get_pdb_tool_file_in_dir(path)
|
|
107
|
+
|
|
108
|
+
for file in tqdm(feature_file_list, desc="Parsing PDB_tool output"):
|
|
109
|
+
df = load_pdb_tool_file(os.path.join(path, file))
|
|
110
|
+
identifier = file.split("-")
|
|
111
|
+
df["Uniprot_ID"] = identifier[1]
|
|
112
|
+
df["F"] = identifier[2].replace("F", "")
|
|
113
|
+
df_list.append(df)
|
|
114
|
+
|
|
115
|
+
return pd.concat(df_list).reset_index(drop=True)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def pdb_tool_to_3s_sse(df):
|
|
119
|
+
"""
|
|
120
|
+
Reduce secondary structure from 8 to 3 states.
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
mapper = {"H":"Helix",
|
|
124
|
+
"G":"Helix",
|
|
125
|
+
"I":"Helix",
|
|
126
|
+
"L":"Coil",
|
|
127
|
+
"T":"Coil",
|
|
128
|
+
"S":"Coil",
|
|
129
|
+
"B":"Coil",
|
|
130
|
+
"E":"Ladder"}
|
|
131
|
+
df["SSE"] = df["SSE"].map(mapper)
|
|
132
|
+
|
|
133
|
+
return df
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def parse_pdb_tool(input_dir : str, output_dir : str):
|
|
137
|
+
"""
|
|
138
|
+
Parse PDB_Tool .feature files inclued in the path into a unique df.
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
pdb_tool_df = load_all_pdb_tool_files(input_dir)
|
|
142
|
+
pdb_tool_df = pdb_tool_to_3s_sse(pdb_tool_df)
|
|
143
|
+
pdb_tool_df = pdb_tool_df.drop(columns=["CLE", "ACC", "CNa", "CNb"])
|
|
144
|
+
pdb_tool_df.to_csv(os.path.join(output_dir, "pdb_tool_df.tsv"), sep="\t", index=False)
|
|
145
|
+
try:
|
|
146
|
+
logger.debug(f"Deleting {input_dir}")
|
|
147
|
+
shutil.rmtree(input_dir)
|
|
148
|
+
except OSError as e:
|
|
149
|
+
logger.debug(f"Cold not delete {input_dir}\nError: {e}")
|
scripts/plotting/pfam.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import subprocess
|
|
4
|
+
|
|
5
|
+
import daiquiri
|
|
6
|
+
import pandas as pd
|
|
7
|
+
|
|
8
|
+
from scripts import __logger_name__
|
|
9
|
+
|
|
10
|
+
logger = daiquiri.getLogger(__logger_name__ + ".plotting.pfam")
|
|
11
|
+
|
|
12
|
+
logging.getLogger('urllib3.connectionpool').setLevel(logging.WARNING)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def add_pfam_metadata(pfam, seq_df):
|
|
17
|
+
"""
|
|
18
|
+
Add Ensembl transcript and gene info and rename cols to
|
|
19
|
+
be merged with Uniprot features dataframe.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
# Add metadata to PFAM
|
|
23
|
+
pfam = seq_df[["Gene", "Uniprot_ID", "Ens_Transcr_ID", "Ens_Gene_ID"]].merge(
|
|
24
|
+
pfam, how="left", on=["Ens_Transcr_ID", "Ens_Gene_ID"])
|
|
25
|
+
pfam = pfam.dropna(how="all", subset=["Pfam_start", "Pfam_end"]).reset_index(drop=True)
|
|
26
|
+
|
|
27
|
+
# Prepare to merge
|
|
28
|
+
pfam["Type"] = "DOMAIN"
|
|
29
|
+
pfam["Evidence"] = "Pfam"
|
|
30
|
+
pfam = pfam.rename(columns={"Pfam_start" : "Begin",
|
|
31
|
+
"Pfam_end" : "End",
|
|
32
|
+
"Pfam_name" : "Description",
|
|
33
|
+
"Pfam_description" : "Full_description"})
|
|
34
|
+
|
|
35
|
+
return pfam
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_pfam(seq_df, output_tsv, organism):
|
|
39
|
+
"""
|
|
40
|
+
Download and parse Pfam coordinates, name, description,
|
|
41
|
+
and Pfam ID to Transcript ID mapping.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
status = "INIT"
|
|
45
|
+
i = 0
|
|
46
|
+
if organism == "Homo sapiens":
|
|
47
|
+
ensembl_gene_dataset = "hsapiens_gene_ensembl"
|
|
48
|
+
elif organism == "Mus musculus":
|
|
49
|
+
ensembl_gene_dataset = "mmusculus_gene_ensembl"
|
|
50
|
+
else:
|
|
51
|
+
logger.error(f"Invalid organism: {organism}. Expected 'Homo sapiens' or 'Mus musculus'.")
|
|
52
|
+
raise ValueError(f"Invalid organism: {organism}. Must be 'Homo sapiens' or 'Mus musculus'.")
|
|
53
|
+
|
|
54
|
+
while status != "PASS":
|
|
55
|
+
if i < 5:
|
|
56
|
+
try:
|
|
57
|
+
# Pfam coordinates
|
|
58
|
+
logger.debug("Downloading and parsing Pfam coordinates...")
|
|
59
|
+
url_query = 'http://jan2024.archive.ensembl.org/biomart/martservice?query='
|
|
60
|
+
query = f'<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE Query><Query virtualSchemaName = "default" formatter = "TSV" header = "0" uniqueRows = "0" count = "" datasetConfigVersion = "0.6" ><Dataset name = "{ensembl_gene_dataset}" interface = "default" ><Attribute name = "ensembl_gene_id" /><Attribute name = "ensembl_transcript_id" /><Attribute name = "pfam_start" /><Attribute name = "pfam_end" /><Attribute name = "pfam" /></Dataset></Query>'
|
|
61
|
+
url = url_query + query
|
|
62
|
+
command = [f"wget", "-q", "-O", "pfam_coordinates.tsv", url]
|
|
63
|
+
subprocess.run(command)
|
|
64
|
+
pfam = pd.read_csv("pfam_coordinates.tsv", sep="\t", header=None)
|
|
65
|
+
pfam.columns = ["Ens_Gene_ID", "Ens_Transcr_ID", "Pfam_start", "Pfam_end", "Pfam_ID"]
|
|
66
|
+
|
|
67
|
+
# ID database
|
|
68
|
+
logger.debug("Downloading and parsing Pfam ID database...")
|
|
69
|
+
url = "https://ftp.ebi.ac.uk/pub/databases/Pfam/current_release/database_files/pfamA.txt.gz"
|
|
70
|
+
command = ["wget", "-q", "-O", "pfam_id.tsv.gz", url]
|
|
71
|
+
subprocess.run(command)
|
|
72
|
+
pfam_id = pd.read_csv("pfam_id.tsv.gz", compression='gzip', sep='\t', header=None).iloc[:,[0,1,3]]
|
|
73
|
+
pfam_id.columns = "Pfam_ID", "Pfam_name", "Pfam_description"
|
|
74
|
+
|
|
75
|
+
# Merge and save
|
|
76
|
+
pfam = pfam.merge(pfam_id, how="left", on="Pfam_ID")
|
|
77
|
+
pfam = pfam.dropna(how="all", subset=["Pfam_start", "Pfam_end"]).reset_index(drop=True)
|
|
78
|
+
pfam = add_pfam_metadata(pfam, seq_df)
|
|
79
|
+
pfam.to_csv(output_tsv, index=False, sep="\t")
|
|
80
|
+
|
|
81
|
+
# Delete temp files
|
|
82
|
+
os.remove("pfam_coordinates.tsv")
|
|
83
|
+
os.remove("pfam_id.tsv.gz")
|
|
84
|
+
status = "PASS"
|
|
85
|
+
|
|
86
|
+
return pfam
|
|
87
|
+
|
|
88
|
+
except Exception as e:
|
|
89
|
+
status = "FAIL"
|
|
90
|
+
logger.warning(f"Error while downloading Pfam: {e}")
|
|
91
|
+
logger.warning("Retrying download...")
|
|
92
|
+
i += 1
|
|
93
|
+
else:
|
|
94
|
+
logger.error(f'Download Pfam: {status}')
|