RefgenDetector 3.0.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.
- refgenDetector/__init__.py +0 -0
- refgenDetector/aligment_files.py +272 -0
- refgenDetector/chromosomes_dict.py +34 -0
- refgenDetector/ref_manager.py +240 -0
- refgenDetector/reference_genome_dictionaries.py +820 -0
- refgenDetector/refgenDetector_main.py +114 -0
- refgenDetector/variant_files.py +363 -0
- refgendetector-3.0.0.dist-info/METADATA +300 -0
- refgendetector-3.0.0.dist-info/RECORD +13 -0
- refgendetector-3.0.0.dist-info/WHEEL +5 -0
- refgendetector-3.0.0.dist-info/entry_points.txt +2 -0
- refgendetector-3.0.0.dist-info/licenses/LICENSE +3 -0
- refgendetector-3.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
""" refgenDetector.py: Script to infer the reference genome used to create a BAM or CRAM"""
|
|
4
|
+
|
|
5
|
+
__author__ = "Mireia Marin Ginestar"
|
|
6
|
+
__version__ = "2.0"
|
|
7
|
+
__maintainer__ = "Mireia Marin Ginestar"
|
|
8
|
+
__email__ = "mireia.marin@crg.eu"
|
|
9
|
+
__status__ = "Developement"
|
|
10
|
+
|
|
11
|
+
version = "2.0.0"
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import sys
|
|
15
|
+
import argparse
|
|
16
|
+
import gzip
|
|
17
|
+
import pysam
|
|
18
|
+
import psutil
|
|
19
|
+
import time
|
|
20
|
+
from rich.console import Console
|
|
21
|
+
|
|
22
|
+
# Add the parent directory to the Python path
|
|
23
|
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
24
|
+
parent_dir = os.path.dirname(current_dir)
|
|
25
|
+
sys.path.insert(0, parent_dir)
|
|
26
|
+
|
|
27
|
+
from reference_genome_dictionaries import *
|
|
28
|
+
from exceptions.NoFileException import *
|
|
29
|
+
from aligment_files import *
|
|
30
|
+
from variant_files import *
|
|
31
|
+
|
|
32
|
+
console = Console()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def monitor_resources(func):
|
|
36
|
+
"""Decorator to print resource usage (CPU, memory, I/O, runtime)."""
|
|
37
|
+
def wrapper(*args, **kwargs):
|
|
38
|
+
process = psutil.Process()
|
|
39
|
+
start_time = time.time()
|
|
40
|
+
start_cpu_time = process.cpu_times()
|
|
41
|
+
|
|
42
|
+
result = func(*args, **kwargs)
|
|
43
|
+
|
|
44
|
+
end_time = time.time()
|
|
45
|
+
end_cpu_time = process.cpu_times()
|
|
46
|
+
duration = end_time - start_time
|
|
47
|
+
|
|
48
|
+
cpu_user = end_cpu_time.user - start_cpu_time.user
|
|
49
|
+
cpu_system = end_cpu_time.system - start_cpu_time.system
|
|
50
|
+
total_cpu_time = cpu_user + cpu_system
|
|
51
|
+
memory_usage = process.memory_info().rss / (1024 * 1024) # MB
|
|
52
|
+
io_counters = process.io_counters()
|
|
53
|
+
bytes_read = io_counters.read_bytes
|
|
54
|
+
bytes_written = io_counters.write_bytes
|
|
55
|
+
|
|
56
|
+
print(f"Execution time: {duration:.2f} seconds")
|
|
57
|
+
print(f"CPU time used: {total_cpu_time:.2f} seconds")
|
|
58
|
+
print(f"Memory usage (RSS): {memory_usage:.2f} MB")
|
|
59
|
+
if bytes_read > (1024*1024):
|
|
60
|
+
print(f"Disk I/O - Read: {bytes_read / (1024 * 1024):.2f} MB, Written: {bytes_written / (1024 * 1024):.2f} MB")
|
|
61
|
+
elif bytes_read > 1024:
|
|
62
|
+
print(f"Disk I/O - Read: {bytes_read / 1024:.2f} KB, Written: {bytes_written / 1024:.2f} KB")
|
|
63
|
+
else:
|
|
64
|
+
print(f"Disk I/O - Read: {bytes_read:.2f} Bytes, Written: {bytes_written:.2f} Bytes")
|
|
65
|
+
|
|
66
|
+
return result
|
|
67
|
+
|
|
68
|
+
return wrapper
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def run_main(args):
|
|
72
|
+
"""Main logic of the tool (isolated from CLI parsing)."""
|
|
73
|
+
console.print(f"[bold]* Running refgenDetector v.{version} *[/bold]")
|
|
74
|
+
console.print(f"---")
|
|
75
|
+
console.print(f"[bold]++ INFORMATION INFERRED BY THE HEADER ++[/bold]\n")
|
|
76
|
+
console.print(f"[bold]File:[/bold] {args.file}")
|
|
77
|
+
try:
|
|
78
|
+
if args.type == "Header":
|
|
79
|
+
console.print("[bold]File type:[/bold] BAM/CRAM header")
|
|
80
|
+
process_data_txt(args.file, args.md5, args.assembly)
|
|
81
|
+
elif args.type in ["VCF"]:
|
|
82
|
+
console.print("[bold]File type:[/bold] VCF")
|
|
83
|
+
open_vcf(args.file, args.matches, args.max_n_var)
|
|
84
|
+
else:
|
|
85
|
+
console.print("[bold]File type:[/bold] BAM/CRAM")
|
|
86
|
+
process_data_bamcram(args.file, args.md5, args.assembly)
|
|
87
|
+
except OSError:
|
|
88
|
+
console.print(f"[red]The file {args.file} provided in --file can't be opened."
|
|
89
|
+
f"\nRun [bold]refgenDetector -h[/bold] to get more information about the usage of the tool.")
|
|
90
|
+
console.print(f"---")
|
|
91
|
+
|
|
92
|
+
def main():
|
|
93
|
+
parser = argparse.ArgumentParser(prog="INFERRING THE REFERENCE GENOME USED TO ALIGN BAM OR CRAM FILE")
|
|
94
|
+
parser.add_argument("-f", "--file", help="Input file path", required=True)
|
|
95
|
+
parser.add_argument("-t", "--type", choices=["BAM/CRAM", "Header", "VCF", "BIM"], required=True,
|
|
96
|
+
help="Type of files to analyze.")
|
|
97
|
+
parser.add_argument("--md5", action="store_true", help="Print md5 values if present in header.")
|
|
98
|
+
parser.add_argument("-a", "--assembly", action="store_true", help="Print assembly if present in header.")
|
|
99
|
+
parser.add_argument("-v", "--max_n_var", type=int, help="Maximum number of variants to read before stopping inference. The file is processed in chunks of 100,000 variants, so this value must be a multiple of 100,000 (e.g. 100000, 200000, 300000, ...).")
|
|
100
|
+
parser.add_argument("-m", "--matches", type=int, default=5000, help="Number of matches required before stopping. [DEFAULT:5000]")
|
|
101
|
+
parser.add_argument("-r", "--resources", action="store_true",
|
|
102
|
+
help="When set, print execution time, CPU, memory, and disk I/O usage.")
|
|
103
|
+
args = parser.parse_args()
|
|
104
|
+
|
|
105
|
+
# Conditional resource monitoring
|
|
106
|
+
if args.resources:
|
|
107
|
+
wrapped = monitor_resources(run_main)
|
|
108
|
+
wrapped(args)
|
|
109
|
+
else:
|
|
110
|
+
run_main(args)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
if __name__ == "__main__":
|
|
114
|
+
main()
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import gzip
|
|
2
|
+
import sys
|
|
3
|
+
import time
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from dns.inet import inet_pton
|
|
6
|
+
from aligment_files import comparison
|
|
7
|
+
from chromosomes_dict import *
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import numpy as np
|
|
12
|
+
import msgpack
|
|
13
|
+
import pysam
|
|
14
|
+
|
|
15
|
+
final_results = []
|
|
16
|
+
console = Console(highlight=False)
|
|
17
|
+
_msgpack_cache = {}
|
|
18
|
+
MSGPACK_DIR = "./msgpacks"
|
|
19
|
+
|
|
20
|
+
def gather_and_sum(lists):
|
|
21
|
+
"""
|
|
22
|
+
It gathers and sums all the matches calculated in get_matches()
|
|
23
|
+
Args:
|
|
24
|
+
lists: list of lists with the matches calculated in get_matches() for each chunk.
|
|
25
|
+
Returns:
|
|
26
|
+
A dictionary with the total number of matches for each version, which is used to infer the reference genome version.
|
|
27
|
+
"""
|
|
28
|
+
cumulative_sums = {}
|
|
29
|
+
for lst in lists:
|
|
30
|
+
for key, value in lst:
|
|
31
|
+
if key in cumulative_sums:
|
|
32
|
+
cumulative_sums[key] += value
|
|
33
|
+
else:
|
|
34
|
+
cumulative_sums[key] = value
|
|
35
|
+
console.print(f"[bold]Matches: [/bold]", cumulative_sums)
|
|
36
|
+
return cumulative_sums
|
|
37
|
+
|
|
38
|
+
def get_matches(snps_dict, chr_):
|
|
39
|
+
"""
|
|
40
|
+
For each chromosome, it looks for the matches of the SNPs in the corresponding msgpack files,
|
|
41
|
+
which contain the reference genome information for each version. It returns a list with the number of matches for each version.
|
|
42
|
+
Args:
|
|
43
|
+
snps_dict: dictionary with the position and the nucleotide of the SNPs in the chunk being processed.
|
|
44
|
+
chr_: chromosome of the chunk being processed, used to load the corresponding msgpack and get the matches.
|
|
45
|
+
Returns:
|
|
46
|
+
A list with the number of matches for each version, which is used to infer the reference genome version.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
global _msgpack_cache
|
|
50
|
+
start = time.time()
|
|
51
|
+
|
|
52
|
+
genome_versions = ["hg18", "GRCh37", "GRCh38", "T2T"]
|
|
53
|
+
matches = []
|
|
54
|
+
|
|
55
|
+
# Extract arrays once
|
|
56
|
+
positions = np.array(list(snps_dict.keys()), dtype=np.int64)
|
|
57
|
+
nucleotides = np.array(list(snps_dict.values()))
|
|
58
|
+
|
|
59
|
+
for version_name in genome_versions:
|
|
60
|
+
cache_key = f"{version_name}-{chr_}"
|
|
61
|
+
|
|
62
|
+
if cache_key not in _msgpack_cache:
|
|
63
|
+
path = f"{MSGPACK_DIR}/{cache_key}.msgpack"
|
|
64
|
+
if not os.path.exists(path):
|
|
65
|
+
continue
|
|
66
|
+
with open(path, "rb") as f:
|
|
67
|
+
_msgpack_cache[cache_key] = msgpack.load(f, raw=False, strict_map_key=False)
|
|
68
|
+
|
|
69
|
+
ref_dict = _msgpack_cache[cache_key]
|
|
70
|
+
|
|
71
|
+
# Vectorized: look up all positions at once, compare arrays
|
|
72
|
+
ref_nucs = np.array([ref_dict.get(p, None) for p in positions])
|
|
73
|
+
match_count = int(np.sum(ref_nucs == nucleotides))
|
|
74
|
+
|
|
75
|
+
matches.append([version_name, match_count])
|
|
76
|
+
|
|
77
|
+
console.print("Getting matches. Took:", time.time() - start, "s")
|
|
78
|
+
return matches
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def trimming_indels(content, ref):
|
|
82
|
+
"""
|
|
83
|
+
If a row is longer than one position it is deleted, deleting this way any indels
|
|
84
|
+
Args:
|
|
85
|
+
content: chunk of the file being read, containing the variants information. It must only contain SNPs, as indels are trimmed in this function.
|
|
86
|
+
ref: column number of the reference allele in the file, which is different in vcfs and bim files.
|
|
87
|
+
Returns:
|
|
88
|
+
A dataframe with only the SNPs in the chunk, and the gVCF status of the chunk.
|
|
89
|
+
"""
|
|
90
|
+
try:
|
|
91
|
+
# Keep only rows where REF is length 1 or <NON_REF>
|
|
92
|
+
del_insertions = content[(content.iloc[:, 2].str.len() == 1) | (content.iloc[:, 2] == '<NON_REF>')]
|
|
93
|
+
|
|
94
|
+
# Keep only rows where ALT is length 1 or <NON_REF>
|
|
95
|
+
vcf_snps = del_insertions[
|
|
96
|
+
(del_insertions.iloc[:, 3].str.len() == 1) | (del_insertions.iloc[:, 3] == '<NON_REF>')]
|
|
97
|
+
|
|
98
|
+
# Select columns and make an explicit copy
|
|
99
|
+
snps = vcf_snps.iloc[:, [1, ref]].copy()
|
|
100
|
+
snps.columns = ['position', 'nucleotide']
|
|
101
|
+
|
|
102
|
+
# Safe assignment using .loc
|
|
103
|
+
snps.loc[:, 'nucleotide'] = snps['nucleotide'].str.upper()
|
|
104
|
+
|
|
105
|
+
gVCF = False
|
|
106
|
+
if content.iloc[:, 3].astype(str).str.contains('<NON_REF>').any():
|
|
107
|
+
gVCF = True
|
|
108
|
+
|
|
109
|
+
return snps, gVCF
|
|
110
|
+
|
|
111
|
+
except Exception:
|
|
112
|
+
console.print("Reference column is empty, please check your input file. Stopping scan.")
|
|
113
|
+
sys.exit(1)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def call_trimming(content, chr):
|
|
117
|
+
"""
|
|
118
|
+
The file must only contain SNPs. This function is necessary because the reference column has a different number in vcfs and in bim files
|
|
119
|
+
Args:
|
|
120
|
+
content: chunk of the file being read, containing the variants information. It must only contain SNPs, as indels are trimmed in the function.
|
|
121
|
+
chr: chromosome of the chunk being processed, used to load the corresponding msgpack and get the matches.
|
|
122
|
+
Returns:
|
|
123
|
+
Calls get_matches() with the SNPs in the chunk, and adds the matches to the global variable final_results. It also returns the gVCF status of the chunk, which is True if any of the variants in the chunk has <NON_REF> in the ALT column, and False otherwise.
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
snps, gVCF = trimming_indels(content, 2) # content : chr pos ref alt
|
|
127
|
+
|
|
128
|
+
if len(snps) != 0:
|
|
129
|
+
snps_dict = dict(zip(snps["position"].astype(int), snps["nucleotide"]))
|
|
130
|
+
results = get_matches(snps_dict, chr) # pass dict, not DataFrame
|
|
131
|
+
final_results.append(results)
|
|
132
|
+
else:
|
|
133
|
+
console.print("There aren't FP SNPs in this chunk", style="bold red")
|
|
134
|
+
|
|
135
|
+
return gVCF
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def read_and_load(chunk):
|
|
139
|
+
"""
|
|
140
|
+
Check the chromosome in the chunk and load only the msgpacks corresponding to that chromosome,
|
|
141
|
+
then call the function to trim indels and get the matches.
|
|
142
|
+
Args:
|
|
143
|
+
chunk: chunk of the file being read, containing the variants information. It must only contain SNPs, as indels are trimmed in the function.
|
|
144
|
+
Returns:
|
|
145
|
+
The gVCF status of the chunk, which is True if any of the variants in the chunk has <NON_REF> in the ALT column, and False otherwise.
|
|
146
|
+
"""
|
|
147
|
+
for chromosome, group_content in chunk.groupby(chunk.columns[0]):
|
|
148
|
+
chromosome_str = str(chromosome)
|
|
149
|
+
if chromosome_str in chromosome_map:
|
|
150
|
+
chr_key = chromosome_map[chromosome_str]
|
|
151
|
+
gVCF = call_trimming(group_content, chr_key)
|
|
152
|
+
console.print("Variants being mapped from:", chr_key)
|
|
153
|
+
|
|
154
|
+
else:
|
|
155
|
+
console.print(f"Chromosome {chromosome_str} not found in chromosome map. Skipping variants from {chromosome_str}.")
|
|
156
|
+
|
|
157
|
+
return gVCF
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def read_chunks(complete_file, cols, n_matches=None, max_n_var=None):
|
|
161
|
+
"""
|
|
162
|
+
Loads the file in batches to avoid loading it completely in memory.
|
|
163
|
+
By default, all variants are read.
|
|
164
|
+
|
|
165
|
+
If `n_matches` is provided (e.g. via -m in argparse), the file will be
|
|
166
|
+
read in chunks until the total number of SNP matches reaches or exceeds
|
|
167
|
+
`n_matches`. At that point the loop stops and the inference is done
|
|
168
|
+
with the matches collected so far.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
complete_file (str): path to the file to read
|
|
172
|
+
cols (list[int]): column indices to load
|
|
173
|
+
n_matches (int | None): total number of matches required before
|
|
174
|
+
stopping early. If None, read all chunks.
|
|
175
|
+
Returns:
|
|
176
|
+
Chunks of 100.000 variants for the inference, until the stopping condition is met (if any).
|
|
177
|
+
"""
|
|
178
|
+
chunk_counter = 0
|
|
179
|
+
results = {}
|
|
180
|
+
gVCF = False # track if any chunk looks like gVCF
|
|
181
|
+
console.print("[bold]\n++ INFORMATION INFERRED BY THE REF COLUMN ++ [/bold]\n ")
|
|
182
|
+
try:
|
|
183
|
+
|
|
184
|
+
for chunk in pd.read_csv(complete_file, sep="\t", comment="#", header=None, chunksize=100000, usecols=cols,):
|
|
185
|
+
chunk_counter = chunk_counter+100000
|
|
186
|
+
# Update global gVCF flag if any chunk reports True
|
|
187
|
+
chunk_gvcf = read_and_load(chunk)
|
|
188
|
+
gVCF = gVCF or chunk_gvcf
|
|
189
|
+
|
|
190
|
+
# Update global results
|
|
191
|
+
results = gather_and_sum(final_results)
|
|
192
|
+
|
|
193
|
+
# If user requested an early stop based on number of variants read
|
|
194
|
+
if max_n_var is not None:
|
|
195
|
+
try:
|
|
196
|
+
if chunk_counter > max_n_var:
|
|
197
|
+
break
|
|
198
|
+
except ValueError:
|
|
199
|
+
console.print("0 FP SPNs in this chunk", style="bold")
|
|
200
|
+
|
|
201
|
+
# If user requested an early stop based on number of matches
|
|
202
|
+
|
|
203
|
+
if n_matches is not None:
|
|
204
|
+
results_matches = sum(results.values()) if results else 0
|
|
205
|
+
if n_matches < results_matches:
|
|
206
|
+
# Enough evidence; stop reading more chunks
|
|
207
|
+
break
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
finally:
|
|
211
|
+
# Inference and messages
|
|
212
|
+
try:
|
|
213
|
+
if not results or max(results.values()) == 0:
|
|
214
|
+
console.print("No SNPs found to infer the reference genome.", style="bold red")
|
|
215
|
+
else:
|
|
216
|
+
best_ref = max(results, key=results.get)
|
|
217
|
+
best_matches = results[best_ref]
|
|
218
|
+
total_matches = sum(results.values())
|
|
219
|
+
|
|
220
|
+
if best_matches > total_matches / 2:
|
|
221
|
+
console.print(
|
|
222
|
+
f"[bold]Inferred Reference genome:[/bold] {best_ref}"
|
|
223
|
+
)
|
|
224
|
+
else:
|
|
225
|
+
console.print(
|
|
226
|
+
"None of the versions has more than 50% of the total matches. "
|
|
227
|
+
"[bold]Reference genome version unknown.[/bold]",
|
|
228
|
+
style="red",
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
except ValueError:
|
|
232
|
+
# Covers cases like max([]) or similar
|
|
233
|
+
console.print("No SNPs found to infer the reference genome.", style="bold red")
|
|
234
|
+
|
|
235
|
+
if gVCF:
|
|
236
|
+
console.print(f"[bold]gVCF by ALT column[/bold]")
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def extract_columns(complete_file, n_matches, max_n_var):
|
|
240
|
+
"""
|
|
241
|
+
Extracts the columns of interest (chr, pos, ref, alt) and sends them to be processed. The inference is done with the matches collected until the stopping condition is met (if any).
|
|
242
|
+
Args:
|
|
243
|
+
complete_file: opened file with the header
|
|
244
|
+
n_matches: stop reading more chunks once the total number of matches reaches this value. By default, 5000 matches are required before stopping.
|
|
245
|
+
max_n_var: stop reading more chunks once the total number of variants read exceeds this value. If None, read all chunks.
|
|
246
|
+
Returns:
|
|
247
|
+
Calls the function to read the file in chunks and process them, with the columns of interest.
|
|
248
|
+
"""
|
|
249
|
+
|
|
250
|
+
cols = [0, 1, 3, 4] # chr pos ref alt
|
|
251
|
+
read_chunks(complete_file, cols, n_matches, max_n_var)
|
|
252
|
+
|
|
253
|
+
def get_n_samples(header):
|
|
254
|
+
"""
|
|
255
|
+
Counts the number of samples in the VCF file by looking at the last header line.
|
|
256
|
+
Args:
|
|
257
|
+
header: list of lines in the header of the VCF file
|
|
258
|
+
Returns:
|
|
259
|
+
The number of samples in the VCF file, inferred by the last header line.
|
|
260
|
+
"""
|
|
261
|
+
|
|
262
|
+
mandatory_columns = ["#CHROM", "POS", "ID", "REF", "ALT", "QUAL", "FILTER", "INFO", "FORMAT"] #fixed fields for variant information
|
|
263
|
+
columns = header[-1].split("\t")
|
|
264
|
+
|
|
265
|
+
try:
|
|
266
|
+
for column in mandatory_columns:
|
|
267
|
+
columns.remove(column)
|
|
268
|
+
n_samples = len(columns)
|
|
269
|
+
console.print(f"[bold]Number of samples:[/bold]", n_samples)
|
|
270
|
+
except ValueError:
|
|
271
|
+
console.print(f"[bold]Number of samples: [/bold]1")
|
|
272
|
+
|
|
273
|
+
def start_refgen_header(header, target_file):
|
|
274
|
+
"""
|
|
275
|
+
Parses the information of the VCF header. If the contig information is present, it is used to infer the reference genome.
|
|
276
|
+
If not, a message is printed and the inference is done with the variants information. If the gVCF tag is present in the header, it is also reported.
|
|
277
|
+
Args:
|
|
278
|
+
header: list of lines in the header of the VCF file
|
|
279
|
+
target_file: path of the input file, used for the messages
|
|
280
|
+
Returns:
|
|
281
|
+
The reference genome inferred by the contig header information, if the file is gVCF according to the header
|
|
282
|
+
"""
|
|
283
|
+
|
|
284
|
+
contig_list = [line for line in header if '##contig' in line and 'length' in line]
|
|
285
|
+
if len(contig_list)!=0:
|
|
286
|
+
contig_list2= [i.split(",") for i in contig_list]
|
|
287
|
+
dict_contigs = {}
|
|
288
|
+
|
|
289
|
+
for line in contig_list2:
|
|
290
|
+
contig_id = None
|
|
291
|
+
contig_length = None
|
|
292
|
+
|
|
293
|
+
for part in line:
|
|
294
|
+
if part.startswith('##contig=<ID='):
|
|
295
|
+
contig_id = part.replace('##contig=<ID=', '')
|
|
296
|
+
elif part.startswith('length='):
|
|
297
|
+
contig_length = int(part.replace('length=', '').replace('>', ''))
|
|
298
|
+
|
|
299
|
+
if contig_id is not None and contig_length is not None:
|
|
300
|
+
dict_contigs[contig_id] = contig_length
|
|
301
|
+
comparison(dict_contigs, target_file) # run the next f
|
|
302
|
+
|
|
303
|
+
else:
|
|
304
|
+
console.print("[dark_orange]Contig information not in the header[/dark_orange] - [bold dark_orange]The reference genome can't be "
|
|
305
|
+
"inferred from the header information [/bold dark_orange]")
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
gVCF= [line for line in header if '##ALT=<ID=NON_REF' in line]
|
|
309
|
+
if gVCF:
|
|
310
|
+
console.print(f"[bold]gVCF according to header[/bold]")
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def extract_header(complete_file, input_file):
|
|
314
|
+
"""
|
|
315
|
+
If present, extracts header and send it to match the refgenDetector database
|
|
316
|
+
Args:
|
|
317
|
+
complete_file: opened file with the header
|
|
318
|
+
Input_file: path of the input file, used for the messages
|
|
319
|
+
Returns:
|
|
320
|
+
The reference genome inferred by the contig header information
|
|
321
|
+
"""
|
|
322
|
+
header = []
|
|
323
|
+
for line in complete_file:
|
|
324
|
+
if line.startswith('#'):
|
|
325
|
+
header.append(line.strip())
|
|
326
|
+
else:
|
|
327
|
+
break
|
|
328
|
+
|
|
329
|
+
start_refgen_header(header, input_file)
|
|
330
|
+
get_n_samples(header)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def open_vcf(input_file, n_matches, max_n_var):
|
|
335
|
+
"""
|
|
336
|
+
Parse arguments and open the input VCF, compressed or not.
|
|
337
|
+
Args:
|
|
338
|
+
input_file: path of the input file
|
|
339
|
+
n_matches (int | None): if provided, the function will stop reading more chunks once the total number of matches reaches. By default, 5000 matches are required before stopping.
|
|
340
|
+
max_n_var (int | None): if provided, the function will stop reading more chunks once the total number of variants read exceeds this value. If None, read all chunks.
|
|
341
|
+
|
|
342
|
+
Returns:
|
|
343
|
+
Calls the function to extract the header and the function to extract the columns of interest and infer the reference genome. The inference is done with the matches collected until the stopping condition is met (if any).
|
|
344
|
+
"""
|
|
345
|
+
|
|
346
|
+
formats = ("vcf")
|
|
347
|
+
compressed_formats = ("vcf.gz") ##TODO whem bgz read_chunks and possible read_and_load take a long time
|
|
348
|
+
|
|
349
|
+
if input_file.endswith(compressed_formats):
|
|
350
|
+
with gzip.open(input_file, "rt") as complete_file:
|
|
351
|
+
extract_header(complete_file, input_file)
|
|
352
|
+
extract_columns(complete_file, n_matches, max_n_var)
|
|
353
|
+
|
|
354
|
+
elif input_file.endswith(formats):
|
|
355
|
+
with open(input_file, "rt") as complete_file:
|
|
356
|
+
extract_header(complete_file, input_file)
|
|
357
|
+
extract_columns(complete_file, n_matches, max_n_var)
|
|
358
|
+
else:
|
|
359
|
+
console.print(f"[bold][red] Only Formats Allowed: vcf and vcf.gz [/red][/bold]")
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
|