hogprof 0.3.0__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,518 @@
1
+ #import profiler
2
+ print("\nNote: this script requires the hogprof environment\n")
3
+ import argparse
4
+ import pandas as pd
5
+ import os
6
+ import sys
7
+ from time import time
8
+ from datetime import datetime
9
+ import numpy as np
10
+ import seaborn as sns
11
+ import re
12
+ from functools import lru_cache
13
+ import csv
14
+ from tqdm import tqdm
15
+ from scipy.spatial.distance import squareform
16
+ from scipy.cluster.hierarchy import linkage
17
+ # test to see if this fixes heatmap issue
18
+ sys.setrecursionlimit(10000)
19
+ from ete3 import NCBITaxa, Tree
20
+ ncbi = NCBITaxa()
21
+
22
+ ### get current time
23
+ start_time_secs = time()
24
+ start_time = datetime.fromtimestamp(start_time_secs).strftime('%Y-%m-%d %H:%M:%S')
25
+ ### print start time human readable
26
+ print(f"Script started at: {start_time}\n")
27
+
28
+ def add_profiler_path(profiler_path):
29
+ """Add the directory containing profiler.py to Python's sys.path"""
30
+ if not os.path.exists(profiler_path):
31
+ raise FileNotFoundError(f"Profiler path does not exist: {profiler_path}")
32
+ profiler_dir = os.path.dirname(os.path.abspath(profiler_path))
33
+ if profiler_dir in sys.path:
34
+ sys.path.remove(profiler_dir)
35
+ sys.path.insert(0, profiler_dir)
36
+
37
+ def create_directory(dirpath):
38
+ if not os.path.exists(dirpath):
39
+ os.mkdir(dirpath)
40
+ return dirpath
41
+
42
+ '''https://stackoverflow.com/questions/46135839/auto-detect-the-delimiter-in-a-csv-file-using-pd-read-csv'''
43
+ def get_delimiter(file_path, bytes = 4096):
44
+ sniffer = csv.Sniffer()
45
+ data = open(file_path, "r").read(bytes)
46
+ delimiter = sniffer.sniff(data).delimiter
47
+ return delimiter
48
+
49
+ def get_hashids(subhogs_table, fam2orthoxml_file):
50
+ sep = get_delimiter(subhogs_table)
51
+ subhogs_df = pd.read_csv(subhogs_table, sep=sep)
52
+ if 'hashid' in subhogs_df.columns:
53
+ return subhogs_table
54
+ print(f"Extracting hashids from file: {fam2orthoxml_file}")
55
+ subhogid_col = "subhogid"
56
+ if subhogid_col not in subhogs_df.columns:
57
+ raise ValueError(f"Column '{subhogid_col}' not found in {subhogs_table}. Please ensure the input file has a column named '{subhogid_col}'.")
58
+ subhogs_df.set_index(subhogid_col, inplace=True)
59
+ ### read in the fam2orthoxml table generated by HogProf lshbuilder and the tree file
60
+ fam2orthoxml_df = pd.read_csv(fam2orthoxml_file)
61
+ # build mapping: subhog_id -> hash row index
62
+ subhog_to_hashid = {subhog: idx for idx, subhog in enumerate(fam2orthoxml_df['subhog_id'])}
63
+ # attach hash ids
64
+ subhogs_df['hashid'] = subhogs_df.index.map(subhog_to_hashid)
65
+ # outputqueryfile
66
+ outputqueryfile = subhogs_table.replace(f"{os.path.splitext(subhogs_table)[1]}",
67
+ f"_queries{os.path.splitext(subhogs_table)[1]}")
68
+ # save new file with hash ids
69
+ subhogs_df.to_csv(outputqueryfile, sep=sep)
70
+ return outputqueryfile
71
+
72
+ def get_hashids_by_subhogid(subhog_ids, fam2orthoxml_file):
73
+ fam2orthoxml_df = pd.read_csv(fam2orthoxml_file)
74
+ subhog_to_hashid = {subhog: idx for idx, subhog in enumerate(fam2orthoxml_df['subhog_id'])}
75
+ hashids = [subhog_to_hashid[subhog] for subhog in subhog_ids if subhog in subhog_to_hashid]
76
+ return hashids
77
+
78
+ '''function to add some info to the extracted hits about how the origin and strength of coevolution'''
79
+ def find_coevolution_origin(hits_df, treepath):
80
+ print("Finding coevolution origins...")
81
+ tree = Tree(treepath, format=1)
82
+ node_lookup = {int(n.name): n for n in tree.traverse() if n.name}
83
+ def edge_to_pair(row):
84
+ return frozenset({
85
+ re.search(r'_(HOG:[^_.]+)', row["query_hog"]).group(1),
86
+ re.search(r'_(HOG:[^_.]+)', row["target_hog"]).group(1)
87
+ })
88
+
89
+ @lru_cache(maxsize=None)
90
+ def lineage(taxid):
91
+ node = node_lookup.get(int(taxid))
92
+ #print("taxid:", taxid, "node:", node)
93
+ if node is None:
94
+ return frozenset()
95
+ return frozenset(int(n.name) for n in node.get_ancestors()) | {int(taxid)}
96
+
97
+ def highest_non_overlapping_taxa(taxids):
98
+ taxids = sorted(set(map(int, taxids)))
99
+ keep = []
100
+ for t in taxids:
101
+ # Remove t if any other observed taxon is an ancestor of it
102
+ if not any(
103
+ other != t and other in lineage(t)
104
+ for other in taxids):
105
+ keep.append(t)
106
+ return keep
107
+ # 1 add column for checking
108
+ hits_df['pair'] = hits_df.apply(edge_to_pair, axis=1)
109
+ # 2 group by pair so that each pair has a list of taxa it appears in
110
+ hits_grouped_df = hits_df.groupby('pair')['taxid'].apply(list).reset_index(name='taxid_list')
111
+ # add column with number of taxids per pair
112
+ hits_grouped_df['families_score'] = hits_grouped_df['taxid_list'].apply(len)
113
+ # 3 find highest rank taxon (or taxa) for each list
114
+ hits_grouped_df["origin_taxids"] = hits_grouped_df["taxid_list"].apply(highest_non_overlapping_taxa)
115
+ # 4 add to hits_df column '1st_occurence' with True for the rows that have a taxid in origin_taxids and False otherwise
116
+ hits_df = hits_df.merge(hits_grouped_df[['pair', 'families_score','origin_taxids']], on='pair', how='left')
117
+ hits_df['1st_occurence'] = hits_df.apply(lambda row: row['taxid'] in row['origin_taxids'], axis=1)
118
+ # 5 remove 'pair' column and 'origin_taxids' column
119
+ hits_df.drop(columns=['pair', 'origin_taxids'], inplace=True)
120
+ return hits_df
121
+
122
+ '''use hashes directly to extract hits'''
123
+ def extract_hits_from_hashes(lshforestpath, hashes_h5, treepath, outputfile, profiler_path,
124
+ allvsall, k, subhogs_table, thresholds_df, treeroot=""):
125
+ # Add profiler directory to path and import
126
+ add_profiler_path(profiler_path)
127
+ import profiler
128
+ print("Imported profiler from:")
129
+ print(profiler.__file__)
130
+ subhogs_df = pd.read_csv(subhogs_table, sep=get_delimiter(subhogs_table), index_col=0)
131
+ ## index label is subhogid
132
+ subhogs_df.index.name = 'subhogid'
133
+ print(subhogs_df.head())
134
+ subhog_to_hashid = subhogs_df['hashid'].to_dict()
135
+ ### check that hashid were indeed extracted
136
+ if any(pd.isnull(list(subhog_to_hashid.values()))):
137
+ print("subhog_to_hashid:",subhog_to_hashid)
138
+ raise ValueError("Some subHOGs do not have hashid. Please check the queries file.")
139
+ hashid_to_subhog = {v: k for k, v in subhog_to_hashid.items()}
140
+ print("\nBuilding the profiler")
141
+ p = profiler.Profiler(lshforestpath = lshforestpath,
142
+ hashes_h5= hashes_h5,
143
+ oma = False ,
144
+ nsamples = 256 ,
145
+ mastertree = treepath,
146
+ slicesubhogs = True
147
+ )
148
+ p.slicesubhogs = False ### setting as False to use original hogid2fam function in profiler.py
149
+ # the reason for this is using family IDS vs hashids for querying. the original function used hashids,
150
+ # sometimes in levels we want to query everything within a family though.
151
+ print("Pulling hashes...")
152
+ raw_profiles = p.pull_hashes(subhog_to_hashid.values()) # returns hashid to hashsig dict
153
+ ### here check that the expected number of hashes was pulled
154
+ if len(raw_profiles)!= len(hashid_to_subhog):
155
+ print("Warning! Could not pull all the hashes!")
156
+ subhogs_without_hash = []
157
+ for subhog, hashid in subhog_to_hashid.items():
158
+ if hashid not in raw_profiles.keys():
159
+ subhogs_without_hash.append(subhog)
160
+ if len(subhogs_without_hash)>0:
161
+ print("Examples:", subhogs_without_hash[:5])
162
+ profiles = {
163
+ hashid_to_subhog[h]: {
164
+ 'hashsig': raw_profiles[h],
165
+ 'hashid': h
166
+ }
167
+ for h in raw_profiles
168
+ if h in hashid_to_subhog
169
+ }
170
+ profiles_df = pd.DataFrame.from_dict(profiles, orient='index')
171
+ profiles_num = len(profiles_df.index.unique())
172
+ ### checking for empty / degenerate /invalid signatures
173
+ profiles_df['sigsum'] = profiles_df.hashsig.map(lambda x : np.sum(x.digest()))
174
+ profiles_df = profiles_df[profiles_df.sigsum>0]
175
+ #print(f'{len(profiles.index.unique())} HOGs have valid signatures')
176
+ if profiles_num != len(profiles_df.index.unique()):
177
+ print("Warning: invalid hash signatures detected!")
178
+
179
+ def get_candidates(p, query_sig, k):
180
+ # query LSH forest for candidate hash ids - get top k
181
+ candidate_subhogids = [target_hog[target_hog.find('_')+1:] for target_hog in p.forest.query(query_sig, k)]
182
+ candidate_hashids = [p.subhog_to_hashids_dict[target_hog] for target_hog in candidate_subhogids]
183
+ local_hashid_to_subhog = {int(h): s for h, s in zip(candidate_hashids, candidate_subhogids)}
184
+ # pull candidate signatures (dict: hashid -> MinHash)
185
+ candidate_hashes = p.pull_hashes(candidate_hashids)
186
+ return candidate_hashids, local_hashid_to_subhog, candidate_hashes
187
+
188
+ def get_scores(candidate_hashids,candidate_hashes, query_sig, level_threshold):
189
+ scores = {}
190
+ for hashid in candidate_hashids: ### candidate_hashids are the closest but they are not ordered
191
+ sig = candidate_hashes[hashid]
192
+ this_score = sig.jaccard(query_sig)
193
+ if this_score >= level_threshold:
194
+ scores[hashid] = this_score
195
+ return scores
196
+
197
+ ### get jaccard scores too
198
+ rows = []
199
+ counter = 0
200
+ no_hits_subhogs = []
201
+ ### data structure_for all_vs_all
202
+ taxa_networks_dict = {}
203
+ network_id_to_hid = {}
204
+ level_to_T_dict = {}
205
+ taxid_to_taxname_dict = thresholds_df['taxname'].to_dict()
206
+ print("Querying each HOG...")
207
+ for query_subhog_id, querydict in profiles.items():
208
+ ### clean hogids e.g. 14_HOG:0004777_537 where 14 is the internal taxid (bin in thresholds)
209
+ # and 537 is the internal hog id from the orthoxml transversal
210
+ query_level = int(query_subhog_id.split("_")[0])
211
+ hog_id_str = query_subhog_id.split("_")[1]
212
+ try:
213
+ level_threshold = thresholds_df['empirical_t'].loc[query_level]
214
+ except:
215
+ level_threshold = 0 ### take everything if no T is specified
216
+ print(f"Warning: no T defined for {query_level}")
217
+ level_to_T_dict[query_level] = level_threshold
218
+ current_taxon = thresholds_df['taxname'].loc[query_level]
219
+ query_sig = querydict['hashsig']
220
+ query_hashid = querydict['hashid']
221
+ # query LSH forest for candidate hash ids - get top k
222
+ candidate_hashids, local_hashid_to_subhog, candidate_hashes = get_candidates(p, query_sig, k)
223
+ scores = get_scores(candidate_hashids,candidate_hashes, query_sig, level_threshold)
224
+ ### it is possible that none pass the threshold
225
+ if len(scores) == 0:
226
+ no_hits_subhogs.append(query_subhog_id)
227
+ continue
228
+ ### it is also possible that all pass the threshold, so we need to check more (only check once more with double k)
229
+ if len(scores) == len(candidate_hashids):
230
+ candidate_hashids, local_hashid_to_subhog, candidate_hashes = get_candidates(p, query_sig, 2*k)
231
+ scores = get_scores(candidate_hashids,candidate_hashes, query_sig, level_threshold)
232
+ # sort candidate scores descending
233
+ scored_pairs = sorted(scores.items(), key=lambda x: x[1], reverse=True)
234
+ '''
235
+ # save info for allvsall
236
+ if allvsall:
237
+ if query_level not in taxa_networks_dict.keys():
238
+ network_id_to_hid = {}
239
+ else:
240
+ network_id_to_hid = taxa_networks_dict[query_level]
241
+ # add new info
242
+ network_id_to_hid[query_subhog_id] = query_hashid
243
+ taxa_networks_dict[query_level] = network_id_to_hid
244
+ '''
245
+ # build rows: one row per query-target pair
246
+ for hid, jaccard in scored_pairs:
247
+ target_subhog = local_hashid_to_subhog[hid]
248
+
249
+ # target_subhog expected like "14_HOG:0004777_537" or "14_751313"
250
+ target_hog_clean = target_subhog # already in desired form
251
+ try:
252
+ target_level = int(target_hog_clean.split("_")[0])
253
+ except Exception:
254
+ # if parsing fails, skip
255
+ print(f"Warning: cannot parse level from {target_hog_clean}; skipping")
256
+ continue
257
+ try:
258
+ target_hog_id_str = target_hog_clean.split("_")[1]
259
+ except Exception:
260
+ target_hog_id_str = ""
261
+ # don't keep self hits:
262
+ if query_subhog_id ==target_hog_clean:
263
+ continue
264
+ # only keep same-level hits so threshold makes sense
265
+ if target_level != query_level:
266
+ continue
267
+ # flag same-family (compare the id string parts)
268
+ same_fam = (hog_id_str == target_hog_id_str)
269
+ # append row
270
+ rows.append({
271
+ 'query_hog': query_subhog_id,
272
+ 'target_hog': target_hog_clean,
273
+ 'jaccard': jaccard,
274
+ 'empirical_t': level_threshold,
275
+ 'taxname': current_taxon,
276
+ 'taxid': query_level,
277
+ 'same_fam': same_fam
278
+ })
279
+ taxa_hsig_dict ={}
280
+ '''
281
+ ### change info for allvsall
282
+ if allvsall:
283
+ for level, network_id_to_hid in taxa_networks_dict.items():
284
+ all_raw_profiles = p.pull_hashes(network_id_to_hid.values()) # returns hashid to hashsig dict
285
+ ### turn into subhog to hashsig
286
+ network_id_to_hsig = {subhogid:all_raw_profiles[hid] for subhogid, hid in network_id_to_hid.items()}
287
+ taxa_hsig_dict[level] = [level_to_T_dict[level],network_id_to_hsig]
288
+ '''
289
+ hits_df = pd.DataFrame(rows)
290
+
291
+ if allvsall:
292
+ target_hogs_per_level = hits_df.groupby('taxid')['target_hog'].unique().to_dict()
293
+ for level, target_hogs in target_hogs_per_level.items():
294
+ query_hogs = hits_df[hits_df['taxid'] == level]['query_hog'].unique()
295
+ target_hogs = np.concatenate([target_hogs, query_hogs])
296
+ level_hashids = get_hashids_by_subhogid(list(target_hogs), fam2orthoxml_file)
297
+ taxa_hsig_dict[level] = [level_to_T_dict[level], p.pull_hashes(level_hashids)]
298
+
299
+ if len(no_hits_subhogs)>0:
300
+ print(f"No hits found for {len(no_hits_subhogs)} subHOGs")
301
+ print("Examples:", no_hits_subhogs[:5])
302
+ else:
303
+ print("All subHOGs had hits")
304
+ print("Total hits found:", len(hits_df))
305
+ print(hits_df.head())
306
+
307
+ ### add column with coevolution origin
308
+ hits_df = find_coevolution_origin(hits_df, treepath)
309
+ # remove taxid column if it exists
310
+ if 'taxid' in hits_df.columns:
311
+ hits_df.drop(columns=['taxid'], inplace=True)
312
+ # replace nan taxname with treeroot if specified
313
+ if treeroot:
314
+ hits_df['taxname'] = hits_df['taxname'].replace(r'^\s*$', np.nan, regex=True)
315
+ hits_df['taxname'] = hits_df['taxname'].fillna(treeroot)
316
+ taxid_to_taxname_dict[0] = treeroot
317
+ print(hits_df.head())
318
+ ### save to file
319
+ hits_df.to_csv(outputfile, index=False)
320
+ print("Created file:", outputfile)
321
+ return taxa_hsig_dict, taxid_to_taxname_dict
322
+
323
+ ### from Dave's jupyter notebook, with changes
324
+ def compare_all_vs_all(taxa_hsig_dict, outputfile):
325
+ print("Performing all vs all comparison...")
326
+ for level, [level_T, network_id_to_hsig] in taxa_hsig_dict.items():
327
+ out_eps = outputfile.replace(".csv",f"{level}.eps")
328
+ ids = list(network_id_to_hsig.keys())
329
+ sigs = [network_id_to_hsig[i] for i in ids]
330
+ jkern = np.array([
331
+ [sig1.jaccard(sig2) if i != j else 0
332
+ for j, sig2 in enumerate(sigs)]
333
+ for i, sig1 in enumerate(sigs)
334
+ ])
335
+ # convert to distance for clustering
336
+ dist = 1 - jkern
337
+ ### now apply threshold
338
+ jkern_plot = jkern.copy()
339
+ jkern_plot[jkern_plot < level_T] = 0
340
+ print(jkern)
341
+ g = sns.clustermap(
342
+ dist,
343
+ xticklabels=ids,
344
+ yticklabels=ids,
345
+ figsize=(20, 20),
346
+ cmap="viridis_r" # reversed so small distance = high similarity
347
+ )
348
+ g.savefig(out_eps, format='eps')
349
+ print(f"Created file: {out_eps}")
350
+
351
+ '''original idea to get the matrix'''
352
+ def jaccard_similarity_matrix(sigs):
353
+ return np.array([[sig1.jaccard(sig2) for sig2 in sigs]
354
+ for sig1 in sigs])
355
+
356
+ '''faster way to get the matrix'''
357
+ def jaccard_similarity_matrix(sigs):
358
+ ### Get the hash values of all signatures as a 2D array
359
+ hashvalues = np.array([sig.hashvalues for sig in sigs])
360
+ #print("hashvalues shape:", hashvalues.shape)
361
+ ### Compare all pairs of signatures
362
+ # Vectorized pairwise Jaccard from matching MinHash values
363
+ jkern = np.mean(hashvalues[:, np.newaxis, :, :] ==
364
+ hashvalues[np.newaxis, :, :, :],
365
+ axis=(2, 3))
366
+ #print("jkern shape:", jkern.shape)
367
+ return jkern
368
+
369
+ '''visualize the similarity matrix as a heatmap with hierarchical clustering'''
370
+ def make_similarity_heatmap(jkern, ids, taxname, outputfile,format="png"):
371
+ dist = 1 - jkern
372
+ # Convert square distance matrix to condensed form for scipy
373
+ condensed_dist = squareform(dist, checks=False)
374
+ # Hierarchical clustering from the precomputed distances
375
+ linkage_matrix = linkage(condensed_dist, method="average")
376
+ g = sns.clustermap(
377
+ jkern,
378
+ row_linkage=linkage_matrix,
379
+ col_linkage=linkage_matrix,
380
+ xticklabels=ids,
381
+ yticklabels=ids,
382
+ figsize=(20, 20),
383
+ cmap="viridis",
384
+ vmin=0,
385
+ vmax=1
386
+ )
387
+ ### only run eps for quite small datasets
388
+ if format == "eps":
389
+ outputfile = outputfile.replace(".csv", f"_{taxname}.eps")
390
+ g.savefig(outputfile, format="eps", bbox_inches='tight')
391
+ ### png is a good default for larger datasets
392
+ elif format == "png":
393
+ outputfile = outputfile.replace(".csv", f"_{taxname}.png")
394
+ g.savefig(outputfile, dpi=600, format="png", bbox_inches='tight')
395
+ else:
396
+ raise ValueError(f"Unsupported format: {format}")
397
+ print(f"Created file: {outputfile}")
398
+
399
+ '''compare all vs all for each taxonomic level'''
400
+ def compare_all_vs_all(taxa_hsig_dict, taxid_to_taxname_dict,outputfile, heatmap=False):
401
+ print("\nPerforming all vs all comparison (untested)...")
402
+ #print(taxa_hsig_dict)
403
+ print(f"Using {len(taxa_hsig_dict)} taxonomic levels for comparison")
404
+ n_profiles = sum(len(network_id_to_hsig)
405
+ for level_T, network_id_to_hsig in taxa_hsig_dict.values())
406
+ print(f"Using {n_profiles} profiles for comparison")
407
+ for level, [level_T, network_id_to_hsig] in tqdm(taxa_hsig_dict.items(),
408
+ desc="Taxonomic levels"):
409
+ taxname = taxid_to_taxname_dict.get(level, f"taxid_{level}")
410
+ ids = list(network_id_to_hsig.keys())
411
+ if len(ids) < 2:
412
+ print(f"Skipping {level}: only {len(ids)} profile(s)")
413
+ continue
414
+ sigs = [network_id_to_hsig[i] for i in ids]
415
+ # similarity matrix
416
+ jkern = jaccard_similarity_matrix(sigs)
417
+ np.fill_diagonal(jkern, 1.0)
418
+ # distance for clustering
419
+ #dist = 1 - jkern
420
+ # --- threshold ONLY for display ---
421
+ #jkern_plot = jkern.copy()
422
+ #jkern_plot[jkern_plot < level_T] = 0
423
+ # threshold only for exported similarity matrix
424
+ sim_df = pd.DataFrame(jkern, index=ids, columns=ids)
425
+ sim_df[sim_df < level_T] = 0
426
+ ### save to csv
427
+ sim_csv = outputfile.replace(".csv", f"_{level}_similarity_matrix.csv")
428
+ sim_df.to_csv(sim_csv)
429
+ print(f"Created similarity matrix file: {sim_csv}")
430
+ if heatmap:
431
+ make_similarity_heatmap(jkern, ids, taxname, outputfile)
432
+
433
+ def main(lshforestpath, hashes_h5, treepath, outputfile, profiler_path, queries_file,thresholds_file,
434
+ allvsall = False, heatmap=False, k= 1000, treeroot=""):
435
+ ### print arguments
436
+ print("\n📋 Running with the following parameters:")
437
+ print(f" • LSH Forest path: {lshforestpath}")
438
+ print(f" • Hashes HDF5: {hashes_h5}")
439
+ print(f" • Tree path: {treepath}")
440
+ print(f" • Output file: {outputfile}")
441
+ print(f" • Profiler path: {profiler_path}")
442
+ print(f" • Queries file: {queries_file}")
443
+ print(f" • All vs all: {allvsall}")
444
+ print(f" • Heatmap: {heatmap}")
445
+ print(f" • k: {k}")
446
+ print(f" • thresholds table: {thresholds_file}")
447
+ print("\n")
448
+
449
+ ### if thresholds are given, take them into account
450
+ thresholds_df = pd.read_csv(thresholds_file)[['bin','empirical_t','taxname']]
451
+ thresholds_df.set_index('bin', inplace=True)
452
+
453
+ create_directory(os.path.dirname(outputfile))
454
+
455
+ ### get hits from hashes
456
+ taxa_hsig_dict, taxid_to_taxname_dict = extract_hits_from_hashes(lshforestpath, hashes_h5, treepath, outputfile, profiler_path,
457
+ allvsall, k, queries_file, thresholds_df, treeroot=treeroot)
458
+ if heatmap and not allvsall:
459
+ print("Warning: heatmap option is only relevant for allvsall mode. Ignoring heatmap option.")
460
+ if allvsall:
461
+ compare_all_vs_all(taxa_hsig_dict, taxid_to_taxname_dict, heatmap=heatmap, outputfile=outputfile.replace(".csv", "_allvsall.csv"))
462
+
463
+ ## Command line argument parsing'''
464
+ def parse_args():
465
+ """
466
+ Parse command line arguments.
467
+ """
468
+ print("Parsing command line arguments")
469
+ parser = argparse.ArgumentParser(description="Extract hits from the profiler for levels mode, per taxon. Queries are subHOGs that can be found in the fam2orthoxml file.")
470
+ parser.add_argument("-i", "--input", required=True, help="HogProf output folder")
471
+ parser.add_argument("-o", "--outputfile", required=True, help="Output CSV file")
472
+ parser.add_argument("--queries_file", required=True, help="Query subHOG table")
473
+ parser.add_argument("--thresholds", required=True, help="Threshold CSV from Step 3")
474
+ parser.add_argument("--profilerpath", default="profiler.py")
475
+ parser.add_argument("--k", type=int, default=1000, help="Number of top hits to extract for each query.")
476
+ parser.add_argument("--allvsall",type=bool, help="Run all vs all mode additionally to the main hit extraction (may take a long time).", default=False)
477
+ parser.add_argument("--heatmap",type=bool, help="Generate a heatmap of the all vs all comparisons.", default=False)
478
+ parser.add_argument("--treeroot", default="", help="Root of the tree to be used in output table as a taxon name. " \
479
+ "The root is often called 'internal_0' so by default, if there is no taxon name it would get an empty string in " \
480
+ "the results table. Specify it here to avoid this")
481
+ return parser.parse_args()
482
+
483
+ if __name__ == "__main__":
484
+ # Parse command line arguments
485
+ args = parse_args()
486
+ #print("Are arguments fixed?")
487
+ inputfolder = args.input
488
+ lshforestpath = os.path.join(inputfolder, "newlshforest.pkl")
489
+ hashes_h5 = os.path.join(inputfolder, "hashes.h5")
490
+ treepath = os.path.join(inputfolder, "speciestree.nwk")
491
+ treepath = os.path.join(inputfolder, "reformatted_tree.nwk")
492
+ fam2orthoxml_file = os.path.join(inputfolder, "fam2orthoxml.csv")
493
+ queries_file = args.queries_file
494
+ # special case for unknown hashids (subhogid per line, identical to fam2orthoxml)
495
+ queries_file = get_hashids(queries_file, fam2orthoxml_file)
496
+ # Run the main function with the parsed arguments
497
+ main(
498
+ lshforestpath=lshforestpath,
499
+ hashes_h5=hashes_h5,
500
+ treepath=treepath,
501
+ outputfile=args.outputfile,
502
+ profiler_path = args.profilerpath,
503
+ allvsall=args.allvsall,
504
+ heatmap=args.heatmap,
505
+ k=args.k,
506
+ queries_file = queries_file,
507
+ thresholds_file=args.thresholds,
508
+ treeroot=args.treeroot
509
+ )
510
+
511
+ ### print end time
512
+ end_time_secs = time()
513
+ end_time = datetime.fromtimestamp(end_time_secs).strftime('%Y-%m-%d %H:%M:%S')
514
+ print(f"Script finished at: {end_time}\n")
515
+ execution_time = end_time_secs - start_time_secs
516
+ ### print execution time but reformat it to be human readable
517
+ print(f"Total execution time: {round(execution_time, 2)} seconds\n")
518
+ print("\nDONE!\n")