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,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}")
@@ -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}')