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,394 @@
1
+ """
2
+ Module including a collection of functions that provide
3
+ general-purpose functionalities that can be used across
4
+ different parts of the dataset building process.
5
+ """
6
+
7
+ import gzip
8
+ import hashlib
9
+ import io
10
+ import os
11
+ import tarfile
12
+ import time
13
+ from zipfile import ZipFile
14
+
15
+ import logging
16
+ import daiquiri
17
+ import numpy as np
18
+ import pandas as pd
19
+ import requests
20
+ from Bio import SeqIO
21
+ from pypdl import Pypdl as Downloader
22
+ import aiohttp
23
+ # from unipressed import IdMappingClient
24
+
25
+ from scripts import __logger_name__
26
+
27
+ logger = daiquiri.getLogger(__logger_name__ + ".build.utils")
28
+
29
+
30
+ # General utils
31
+
32
+ def rounded_up_division(num, den):
33
+ """
34
+ Perform a division and round up the result.
35
+ """
36
+
37
+ return -(-num // den)
38
+
39
+
40
+ def get_pos_fragments(mut_gene_df):
41
+ """
42
+ Get the corresponding fragment of each position of the protein.
43
+ """
44
+
45
+ max_f = rounded_up_division(max(mut_gene_df["Pos"]), 1400)
46
+ bins = [n * 1400 for n in range(0, max_f+1)]
47
+ group_names = list(range(1, max_f+1))
48
+
49
+ return pd.cut(mut_gene_df["Pos"], bins, labels = group_names)
50
+
51
+
52
+ def get_species(species):
53
+ """
54
+ Change species name to accepted format.
55
+ """
56
+
57
+ if species == "human" or species.capitalize() == "Homo sapiens":
58
+ species = "Homo sapiens"
59
+ elif species == "mouse" or species.capitalize() == "Mus musculus":
60
+ species = "Mus musculus"
61
+ else:
62
+ raise RuntimeError(
63
+ f"Failed to recognize '{species}' as species. Currently accepted ones are 'Homo sapiens' and 'Mus musculus'. Exiting.."
64
+ )
65
+
66
+ return species
67
+
68
+
69
+ # Download
70
+
71
+ CHECKSUM = {
72
+ "UP000005640_9606_HUMAN_v4": "bf62d5402cb1c4580d219335a9af1ac831416edfbf2892391c8197a8356091f2",
73
+ "UP000000589_10090_MOUSE_v4" : "eb6c529c8757d511b75f4856c1a789378478e6255a442517ad8579708787bbab",
74
+ "mane_overlap_v4" : "c01e9b858c5415cfe2eae7e52a561aa8a872ba0d5d4891ba0cec327b3af49d69"
75
+ }
76
+
77
+
78
+ def assert_proteome_integrity(file_path, proteome):
79
+
80
+ if proteome in CHECKSUM.keys():
81
+ logger.debug('Asserting integrity of file..')
82
+ try:
83
+ if CHECKSUM[proteome] == calculate_hash(file_path):
84
+ logger.debug('File integrity check: PASS')
85
+ return "PASS"
86
+ else:
87
+ logger.debug('File integrity check: FAIL')
88
+ return "FAIL"
89
+ except Exception as e:
90
+ logger.debug('File integrity check: FAIL')
91
+ logger.debug(f'Error: {e}')
92
+ return "FAIL"
93
+ else:
94
+ logger.warning("Assertion skipped: Proteome checksum not in records.")
95
+ return "PASS"
96
+
97
+
98
+ def calculate_hash(filepath: str, hash_func=hashlib.sha256) -> str:
99
+ """
100
+ Calculate the hash of a file using the specified hash function.
101
+
102
+ Args:
103
+ filepath (str): The path to the file for which to calculate the hash.
104
+ hash_func (hashlib.Hash): The hash function to use (default is hashlib.sha256).
105
+
106
+ Returns:
107
+ str: The hexadecimal representation of the calculated hash.
108
+ """
109
+
110
+ with open(filepath, 'rb') as file:
111
+ hash_obj = hash_func()
112
+ for chunk in iter(lambda: file.read(8192), b''):
113
+ hash_obj.update(chunk)
114
+ return hash_obj.hexdigest()
115
+
116
+
117
+ def download_single_file(url: str, destination: str, threads: int, proteome=None) -> None:
118
+ """
119
+ Downloads a file from a URL and saves it to the specified destination.
120
+
121
+ Args:
122
+ url (str): The URL of the file to download.
123
+ destination (str): The local path where the file will be saved.
124
+ """
125
+
126
+ num_connections = 15 if threads > 40 else threads
127
+
128
+ if os.path.exists(destination):
129
+ logger.debug(f"File {destination} already exists..")
130
+ if proteome is not None:
131
+ status = assert_proteome_integrity(destination, proteome)
132
+ if status == "PASS":
133
+ logger.debug(f"File {destination} already exists: Skipping download..")
134
+ return None
135
+ else:
136
+ logger.debug(f"File {destination} already exists but failed integrity check: Retrying download..")
137
+
138
+ logger.debug(f'Downloading {url}')
139
+ logger.debug(f"Downloading to {destination}")
140
+ dl = Downloader(timeout=aiohttp.ClientTimeout(sock_read=400), ssl=False)
141
+ dl.start(url, destination, segments=num_connections, display=True, retries=10, clear_terminal=False)
142
+ logger.debug('Download complete')
143
+
144
+
145
+ def extract_tar_file(file_path, path):
146
+
147
+ checkpoint = os.path.join(path, ".tar_checkpoint.txt")
148
+
149
+ if os.path.exists(checkpoint):
150
+ logger.debug('Tar already extracted: Skipping..')
151
+ else:
152
+ with tarfile.open(file_path, "r") as tar:
153
+ tar.extractall(path)
154
+ logger.debug(f'Extracted { int(len(tar.getnames())/2)} structure.')
155
+ with open(checkpoint, "w") as f:
156
+ f.write('')
157
+
158
+
159
+ def extract_zip_file(file_path, path):
160
+ checkpoint = os.path.join(path, ".zip_checkpoint.txt")
161
+
162
+ if os.path.exists(checkpoint):
163
+ logger.debug('ZIP file already extracted: Skipping..')
164
+ else:
165
+ with ZipFile(file_path, "r") as zip_file:
166
+ zip_file.extractall(path)
167
+ logger.debug(f'Extracted {len(zip_file.namelist())} files.')
168
+ with open(checkpoint, "w") as f:
169
+ f.write('')
170
+
171
+
172
+ # PDB
173
+
174
+ def get_af_id_from_pdb(path_structure):
175
+ """
176
+ Get AlphaFold 2 identifier (UniprotID_F) from path
177
+ """
178
+
179
+ return path_structure.split("AF-")[1].split("-model")[0]
180
+
181
+
182
+ def get_seq_from_pdb(path_structure):
183
+ """
184
+ Get sequense of amino acid residues from PDB structure.
185
+ """
186
+ if path_structure.endswith("gz"):
187
+ with gzip.open(path_structure, 'rt') as handle:
188
+ return np.array([record.seq for record in SeqIO.parse(handle, 'pdb-seqres')][0])
189
+ else:
190
+ with open(path_structure, 'r') as handle:
191
+ return np.array([record.seq for record in SeqIO.parse(handle, 'pdb-seqres')][0])
192
+
193
+
194
+ def get_pdb_path_list_from_dir(path_dir):
195
+ """
196
+ Takes as input a path of a given directory and it
197
+ outputs a list of paths of the contained PDB files.
198
+ """
199
+
200
+ pdb_files = os.listdir(path_dir)
201
+ pdb_path_list = [f"{os.path.join(path_dir, f)}" for f in pdb_files if ".pdb" in f and not os.path.isdir(os.path.join(path_dir, f))]
202
+ return pdb_path_list
203
+
204
+
205
+ # Uniprot ID to Hugo symbols mapping
206
+
207
+ def split_lst_into_chunks(lst, batch_size = 5000):
208
+ """
209
+ Simple split a list into list of list of chunk_size elements.
210
+ """
211
+
212
+ return [lst[i:i+batch_size] for i in range(0, len(lst), batch_size)]
213
+
214
+
215
+ def convert_dict_hugo_to_uniprot(dict_uniprot_hugo):
216
+ """
217
+ Convert a Uniprot IDs to Hugo symbol dictionary to a Hugo symbo to
218
+ Uniprot IDs dictionary, if multiple Hugo symbols are mapped to the
219
+ same Uniprot ID, add them as multiple keys.
220
+ """
221
+
222
+ dict_hugo_uniprot = {}
223
+
224
+ for uni_id, gene in dict_uniprot_hugo.items():
225
+ if type(gene) == str:
226
+ for g in gene.split(" "):
227
+ dict_hugo_uniprot[g] = uni_id
228
+
229
+ return dict_hugo_uniprot
230
+
231
+
232
+ def uniprot_to_hudo_df(uniprot_ids):
233
+ """
234
+ Given a list of Uniprot IDs (from any species), request an Id
235
+ mapping job to UniprotKB to retrieve the corresponding Hugo
236
+ symbols and additional protein info. Return a pandas dataframe.
237
+ It is recommended to provide batches of IDs up to 5000 elements.
238
+ """
239
+
240
+ job_id = get_mapping_jobid(uniprot_ids)
241
+ url = f"https://rest.uniprot.org/idmapping/uniprotkb/results/stream/{job_id}?compressed=true&fields=accession%2Cid%2Cprotein_name%2Cgene_names%2Corganism_name%2Clength&format=tsv"
242
+ df = load_df_from_url(url)
243
+
244
+ i = 60
245
+ while df is None:
246
+ time.sleep(1)
247
+ df = load_df_from_url(url)
248
+ if i % 180 == 0:
249
+ logger.debug("Waiting for UniprotKB mapping job to produce url..")
250
+ i += 1
251
+
252
+ return df
253
+
254
+
255
+ def load_df_from_url(url):
256
+ """
257
+ Load a pandas dataframe from url.
258
+ """
259
+
260
+ try:
261
+ response = requests.get(url, timeout=(160, 160))
262
+ decompressed_data = gzip.decompress(response.content)
263
+ df = pd.read_csv(io.BytesIO(decompressed_data), sep='\t')
264
+ except:
265
+ df = None
266
+
267
+ return df
268
+
269
+
270
+ def get_response_jobid(response):
271
+ """
272
+ Get jobId after submitting ID Mapping job to UniprotKB.
273
+ """
274
+
275
+ try:
276
+ data = response.json()
277
+ job_id = data.get("jobId")
278
+ except:
279
+ job_id = None
280
+
281
+ return job_id
282
+
283
+
284
+ def get_mapping_jobid(uniprot_ids):
285
+ """
286
+ Submit an ID Mapping job to UniprotKB.
287
+ """
288
+
289
+ command = f"https://rest.uniprot.org/idmapping/run?from=UniProtKB_AC-ID&to=UniProtKB&ids={','.join(uniprot_ids)}"
290
+
291
+ response = "INIT"
292
+ while str(response) != "<Response [200]>":
293
+ if response != "INIT":
294
+ time.sleep(10)
295
+ try:
296
+ response = requests.post(command)
297
+ except requests.exceptions.RequestException as e:
298
+ response = "ERROR"
299
+ logger.debug(f"Request failed {e}: Retrying")
300
+
301
+ job_id = get_response_jobid(response)
302
+ i = 60
303
+ while job_id is None:
304
+ time.sleep(1)
305
+ job_id = get_response_jobid(response)
306
+ if i % 60 == 0:
307
+ logger.debug("Requesting ID mapping job to UniprotKB for IDs..")
308
+ i += 1
309
+
310
+ return job_id
311
+
312
+
313
+ def uniprot_to_hugo(uniprot_ids, hugo_as_keys=False, batch_size=5000):
314
+ """
315
+ Given a list of Uniprot IDs (any species.), request an Id mapping
316
+ job to UniprotKB to retrieve the corresponding Hugo symbols.
317
+ Return a dictionary of Uniprot IDs to Hugo symbols or vice versa.
318
+ """
319
+
320
+ # Split uniprot IDs into chunks
321
+ uniprot_ids_lst = split_lst_into_chunks(uniprot_ids, batch_size)
322
+
323
+ # Get a dataframe including all IDs mapping info
324
+ df_lst = []
325
+ for i, ids in enumerate(uniprot_ids_lst):
326
+ logger.debug(f"Batch {i+1}/{len(uniprot_ids_lst)} ({len(ids)} IDs)..")
327
+ df = uniprot_to_hudo_df(ids)
328
+ df_lst.append(df)
329
+ df = pd.concat(df_lst)
330
+
331
+ # Get a dictionary for Uniprot ID to Hugo symbols
332
+ dictio = {}
333
+ for i, r in df[["Entry", "Gene Names"]].iterrows():
334
+ uni_id, gene = r
335
+ dictio[uni_id] = gene
336
+
337
+ # Convert to a dictionary of Hugo symbols to Uniprot IDs
338
+ if hugo_as_keys:
339
+ dictio = convert_dict_hugo_to_uniprot(dictio)
340
+
341
+ return dictio
342
+
343
+
344
+ # def uniprot_to_hugo_pressed(uniprot_ids, hugo_as_keys=False, batch_size=5000, max_attempts=15):
345
+ # """
346
+ # Given a list of Uniprot IDs (any species.), return a
347
+ # dictionary of Uniprot IDs to Hugo symbols or vice versa.
348
+ # """
349
+
350
+ # # Split uniprot IDs into chunks
351
+ # uniprot_ids_lst = split_lst_into_chunks(uniprot_ids, batch_size)
352
+
353
+ # # Get a dataframe including all IDs mapping info
354
+ # result_lst = []
355
+
356
+ # for i, ids in enumerate(uniprot_ids_lst):
357
+ # logger.debug(f"Batch {i+1}/{len(uniprot_ids_lst)} ({len(ids)} IDs)..")
358
+ # status = "INIT"
359
+
360
+ # n = 0
361
+ # while status != "FINISHED":
362
+ # try:
363
+ # request = IdMappingClient.submit(source="UniProtKB_AC-ID",
364
+ # dest="Gene_Name",
365
+ # ids={uni_id for uni_id in ids})
366
+ # j = 0
367
+ # while status != "FINISHED":
368
+ # time.sleep(15)
369
+ # status = request.get_status()
370
+ # if status == "FINISHED":
371
+ # result_lst.append(list(request.each_result()))
372
+ # else:
373
+ # logger.debug(f"Waiting for UniprotKB to process the job..")
374
+ # j += 1
375
+ # if j == max_attempts:
376
+ # logger.debug(f"Failed to obtain Uniprot ID to Hugo Symbol mapping: Retrying..")
377
+ # sys.exit()
378
+ # except Exception as e:
379
+ # n += 1
380
+ # if n == max_attempts:
381
+ # logger.error(f"Failed to obtain Uniprot ID to Hugo Symbol mapping and reached maximum attempts: Exiting..")
382
+ # sys.exit()
383
+ # else:
384
+ # status = "ERROR"
385
+ # logger.debug(f"Error while obtaining Uniprot ID to Hugo Symbol mapping {e}: Retrying..")
386
+
387
+ # result_lst = [entry for batch in result_lst for entry in batch]
388
+ # result_dict = {r["from"] : (r["to"].split()[0] if len(r["to"].split()) > 1 else r["to"]) for r in result_lst}
389
+
390
+ # # Convert to a dictionary of Hugo symbols to Uniprot IDs
391
+ # if hugo_as_keys:
392
+ # result_dict = convert_dict_hugo_to_uniprot(result_dict)
393
+
394
+ # return result_dict
scripts/globals.py ADDED
@@ -0,0 +1,169 @@
1
+ import os
2
+ import logging
3
+ import daiquiri
4
+ import click
5
+ import shutil
6
+ from datetime import datetime
7
+
8
+ from functools import wraps
9
+
10
+ from scripts import __logger_name__
11
+
12
+
13
+ logger = daiquiri.getLogger(__logger_name__)
14
+
15
+ DATE = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
16
+ FORMAT = "%(asctime)s - %(color)s%(levelname)-7s%(color_stop)s | %(name)s - %(color)s%(message)s%(color_stop)s"
17
+
18
+
19
+ # =========
20
+ # Logging
21
+ # =========
22
+
23
+ def setup_logging_decorator(func):
24
+ @wraps(func)
25
+ def wrapper(*args, **kwargs):
26
+ log_dir = os.path.join(click.get_current_context().params['output_dir'], 'log')
27
+ command_name = click.get_current_context().command.name
28
+
29
+ if command_name in ['run', 'plot', 'chimerax_plot', 'chimerax-plot']:
30
+ cohort = click.get_current_context().params["cohort"]
31
+ fname = f'{command_name}_{cohort if cohort != "None" else ""}_{DATE}.log'
32
+ else:
33
+ fname = f"{command_name}_{DATE}.log"
34
+
35
+ os.makedirs(log_dir, exist_ok=True)
36
+
37
+ level = logging.DEBUG if click.get_current_context().params['verbose'] else logging.INFO
38
+
39
+ formatter = daiquiri.formatter.ColorFormatter(fmt=FORMAT)
40
+
41
+ daiquiri.setup(level=level, outputs=(
42
+ daiquiri.output.Stream(formatter=formatter),
43
+ daiquiri.output.File(filename=os.path.join(log_dir, fname), formatter=formatter)
44
+ ))
45
+
46
+ return func(*args, **kwargs)
47
+
48
+ return wrapper
49
+
50
+
51
+ def startup_message(version, initializing_text):
52
+
53
+ author = "Biomedical Genomics Lab - IRB Barcelona"
54
+ support_email = "stefano.pellegrini@irbbarcelona.org"
55
+ banner_width = 70
56
+
57
+ logger.info("#" * banner_width)
58
+ logger.info(f"{'#' + ' ' * (banner_width - 2) + '#'}")
59
+ logger.info(f"{'#' + f'Welcome to Oncodrive3D!'.center(banner_width - 2) + '#'}")
60
+ logger.info(f"{'#' + ' ' * (banner_width - 2) + '#'}")
61
+ logger.info(f"{'#' + initializing_text.center(banner_width - 2) + '#'}")
62
+ logger.info(f"{'#' + ' ' * (banner_width - 2) + '#'}")
63
+ logger.info(f"{'#' + f'Version: {version}'.center(banner_width - 2) + '#'}")
64
+ logger.info(f"{'#' + f'Author: {author}'.center(banner_width - 2) + '#'}")
65
+ logger.info(f"{'#' + f'Support: {support_email}'.center(banner_width - 2) + '#'}")
66
+ logger.info(f"{'#' + ' ' * (banner_width - 2) + '#'}")
67
+ logger.info("#" * banner_width)
68
+ logger.info("")
69
+
70
+
71
+ # ===================
72
+ # Clean and organize
73
+ # ===================
74
+
75
+
76
+ def copy_dir(source_dir, destination_dir):
77
+ """
78
+ Ccopy the entire directory.
79
+ """
80
+
81
+ logger.debug("Copying directory..")
82
+ logger.debug(f"From {source_dir}")
83
+ logger.debug(f"To {destination_dir}")
84
+
85
+ if os.path.exists(source_dir):
86
+ if os.path.isdir(source_dir):
87
+
88
+ try:
89
+ shutil.copytree(source_dir, destination_dir)
90
+ logger.debug("Directory copied successfully!")
91
+ except Exception as e:
92
+ logger.warning(f"An error occurred ({e}): Skipping")
93
+
94
+ else:
95
+ logger.warning("Error while copying directory (source path is not a directory): Skipping")
96
+ else:
97
+ logger.warning("Error while copying directory (source path does not exist): Skipping")
98
+
99
+
100
+ def rm_dir(dir_path): # TO DO: Probably, if the directory is not empty, it should ask for confirmation
101
+ """
102
+ Remove directory.
103
+ """
104
+
105
+ if os.path.isdir(dir_path):
106
+ shutil.rmtree(dir_path, ignore_errors=True)
107
+
108
+
109
+ def rm_files(dir_path, ext=[".cif.gz", ".cif"]) -> None:
110
+ """
111
+ Remove any file with a given extension in a given directory.
112
+ """
113
+
114
+ for file in os.listdir(dir_path):
115
+ file_path = os.path.join(dir_path, file)
116
+ if any([file.endswith(ext_i) for ext_i in ext]):
117
+ os.remove(file_path)
118
+
119
+
120
+ def clean_directory(path: str, loc: str) -> None:
121
+ """
122
+ Clean a directory by removing specific files and subdirectories.
123
+ """
124
+
125
+ if loc == "d":
126
+ logger.debug(f"Cleaning {path}")
127
+ rm_files(path, ext=[".tsv", ".csv", ".json", ".txt", ".txt.gz"])
128
+ dirs = "pae", "pdb_structures", "pdb_structures_mane", "prob_cmaps"
129
+ path_dirs = [os.path.join(path, d) for d in dirs]
130
+ for path in path_dirs:
131
+ rm_dir(path)
132
+
133
+ elif loc == "r":
134
+ # TODO: implement cleaning function for output
135
+ pass
136
+
137
+
138
+ def clean_dir(path: str, loc: str = 'd', txt_file=False) -> None:
139
+ """
140
+ Clean it upon request if it already exists.
141
+ """
142
+
143
+ if os.listdir(path) != ['log']:
144
+ logger.warning(f"Directory {path} already exists and is not empty.")
145
+
146
+ overwrite = "y" if click.get_current_context().params['yes'] else input("Clean existing directory? (y/n): ")
147
+ while overwrite.lower() not in ["y", "yes", "n", "no"]:
148
+ print("Please choose yes or no")
149
+ overwrite = input("Clean existing directory? (y/n): ")
150
+
151
+ if overwrite.lower() in ["y", "yes"]:
152
+ clean_directory(path, loc)
153
+ logger.info(f"Dataset files in {path} have been removed.")
154
+ else:
155
+ logger.warning(f"Dataset files in {path} have not been removed.")
156
+ else:
157
+ pass
158
+
159
+
160
+ def clean_temp_files(path: str) -> None:
161
+ """
162
+ Clean temp files from dir after completing building the datasets.
163
+ """
164
+
165
+ pdb_dir = os.path.join(path, "pdb_structures")
166
+ rm_files(pdb_dir, ext=[".cif.gz", ".cif", ".tar"])
167
+ rm_files(os.path.join(path, "pae"), ext=[".json"])
168
+ rm_dir(os.path.join(path, "pdb_structures_mane"))
169
+ rm_dir(os.path.join(pdb_dir, "fragmented_pdbs"))