0
|
1 #!/usr/bin/env python
|
|
2
|
|
3 import argparse
|
|
4 import csv
|
|
5 import os
|
4
|
6 import subprocess
|
|
7 import sys
|
|
8 import tempfile
|
0
|
9
|
4
|
10 import Bio.SeqIO
|
0
|
11 import numpy
|
|
12 import pandas
|
|
13 import matplotlib.pyplot as pyplot
|
|
14
|
|
15
|
1
|
16 def get_amr_in_feature_hits(amr_feature_hits):
|
|
17 for k in amr_feature_hits.keys():
|
0
|
18 if k.lower().find('amr') >= 0:
|
1
|
19 return amr_feature_hits[k]
|
0
|
20 return None
|
|
21
|
|
22
|
4
|
23 def load_fasta(fasta_file):
|
|
24 sequence = pandas.Series(dtype=object)
|
|
25 for contig in Bio.SeqIO.parse(fasta_file, 'fasta'):
|
|
26 sequence[contig.id] = contig
|
|
27 return sequence
|
|
28
|
|
29
|
|
30 def run_command(cmd):
|
|
31 try:
|
|
32 tmp_name = tempfile.NamedTemporaryFile(dir=".").name
|
|
33 tmp_stderr = open(tmp_name, 'wb')
|
|
34 proc = subprocess.Popen(args=cmd, shell=True, stderr=tmp_stderr.fileno())
|
|
35 returncode = proc.wait()
|
|
36 tmp_stderr.close()
|
|
37 if returncode != 0:
|
|
38 # Get stderr, allowing for case where it's very large.
|
|
39 tmp_stderr = open(tmp_name, 'rb')
|
|
40 stderr = ''
|
|
41 buffsize = 1048576
|
|
42 try:
|
|
43 while True:
|
|
44 stderr += tmp_stderr.read(buffsize)
|
|
45 if not stderr or len(stderr) % buffsize != 0:
|
|
46 break
|
|
47 except OverflowError:
|
|
48 pass
|
|
49 tmp_stderr.close()
|
|
50 os.remove(tmp_name)
|
|
51 stop_err(stderr)
|
|
52 except Exception as e:
|
|
53 stop_err('Command:\n%s\n\nended with error:\n%s\n\n' % (cmd, str(e)))
|
|
54
|
|
55
|
|
56 def stop_err(msg):
|
|
57 sys.stderr.write(msg)
|
|
58 sys.exit(1)
|
|
59
|
|
60
|
7
|
61 def draw_amr_matrix(amr_feature_hits_files, amr_deletions_file, varscan_vcf_file, amr_mutation_regions_bed_file, amr_gene_drug_file, reference, reference_size, mutation_regions_dir, amr_matrix_png_dir):
|
0
|
62 ofh = open('process_log', 'w')
|
|
63
|
1
|
64 # Read amr_feature_hits_files.
|
|
65 amr_feature_hits = pandas.Series(dtype=object)
|
|
66 for amr_feature_hits_file in amr_feature_hits_files:
|
|
67 feature_name = os.path.basename(amr_feature_hits_file)
|
0
|
68 # Make sure the file is not empty.
|
1
|
69 if os.path.isfile(amr_feature_hits_file) and os.path.getsize(amr_feature_hits_file) > 0:
|
|
70 best_hits = pandas.read_csv(filepath_or_buffer=amr_feature_hits_file, sep='\t', header=None)
|
|
71 ofh.write("\nFeature file %s will be processed\n" % os.path.basename(amr_feature_hits_file))
|
0
|
72 else:
|
1
|
73 ofh.write("\nEmpty feature file %s will NOT be processed\n" % os.path.basename(amr_feature_hits_file))
|
0
|
74 best_hits = None
|
1
|
75 amr_feature_hits[feature_name] = best_hits
|
0
|
76
|
1
|
77 amr_hits = get_amr_in_feature_hits(amr_feature_hits)
|
0
|
78 ofh.write("\namr_hits:\n%s\n" % str(amr_hits))
|
|
79 if amr_hits is not None:
|
|
80 amr_to_draw = pandas.DataFrame(columns=['gene', 'drug'])
|
|
81 ofh.write("\namr_to_draw:\n%s\n" % str(amr_to_draw))
|
|
82 # Read amr_drug_gene_file.
|
|
83 amr_gene_drug = pandas.read_csv(amr_gene_drug_file, index_col=None, sep='\t', quoting=csv.QUOTE_NONE, header=None)
|
|
84 ofh.write("\namr_gene_drug:\n%s\n" % str(amr_gene_drug))
|
|
85
|
|
86 # Roll up AMR gene hits.
|
4
|
87 ofh.write("\namr_hits.shape[0]: %s\n" % str(amr_hits.shape[0]))
|
0
|
88 if amr_hits.shape[0] > 0:
|
|
89 for gene_idx, gene in amr_hits.iterrows():
|
4
|
90 ofh.write("gene_idx: %s\n" % str(gene_idx))
|
|
91 ofh.write("gene: %s\n" % str(gene))
|
0
|
92 gene_name = gene[3]
|
|
93 ofh.write("gene_name: %s\n" % str(gene_name))
|
|
94 ofh.write("amr_gene_drug[0]: %s\n" % str(amr_gene_drug[0]))
|
|
95 drugs = amr_gene_drug.loc[amr_gene_drug[0] == gene_name, :][1]
|
4
|
96 ofh.write("drugs: %s\n" % str(drugs))
|
0
|
97 for drug in drugs:
|
|
98 amr_to_draw = amr_to_draw.append(pandas.Series([gene_name, drug], name=amr_to_draw.shape[0], index=amr_to_draw.columns))
|
4
|
99 ofh.write("\amr_to_draw: %s\n" % str(amr_to_draw))
|
0
|
100
|
7
|
101 ofh.write("\nvarscan_vcf_file si None: %s\n" % str(varscan_vcf_file == 'None'))
|
|
102 if varscan_vcf_file not in [None, 'None'] and os.path.getsize(varscan_vcf_file) > 0:
|
4
|
103 amr_mutations = pandas.Series(dtype=object)
|
7
|
104 if amr_mutation_regions_bed_file is not None:
|
|
105 mutation_regions = pandas.read_csv(amr_mutation_regions_bed_file, header=0, sep='\t', index_col=False)
|
4
|
106 if mutation_regions.shape[1] != 7:
|
|
107 ofh.write("\nMutation regions should be a six column file.\n")
|
|
108 elif mutation_regions.shape[0] == 0:
|
|
109 ofh.write("\nNo rows in mutation regions file.\n")
|
|
110 else:
|
|
111 for region_i in range(mutation_regions.shape[0]):
|
|
112 region = mutation_regions.iloc[region_i, :]
|
|
113 if not region.get('type', default='No Type') in ['snp', 'small-indel', 'any']:
|
|
114 continue
|
|
115 ofh.write("\nFinding AMR mutations for %s.\n" % str(region['name']))
|
7
|
116 region_bed = 'region_%s.bed' % region_i
|
4
|
117 mutation_regions.loc[[region_i], ].to_csv(path_or_buf=region_bed, sep='\t', header=False, index=False)
|
7
|
118 region_mutations_tsv = os.path.join(mutation_regions_dir, 'region_%s_mutations.tsv' % region_i)
|
6
|
119 cmd = ' '.join(['bedtools intersect -nonamecheck -wb -a',
|
|
120 region_bed,
|
|
121 '-b',
|
7
|
122 varscan_vcf_file,
|
|
123 ' | awk \'BEGIN{getline < "' + amr_mutation_regions_bed_file + '";printf $0"\\t";',
|
|
124 'getline < "' + varscan_vcf_file + '"; getline < "' + varscan_vcf_file + '";print $0}{print}\'',
|
|
125 '1>' + region_mutations_tsv])
|
6
|
126 ofh.write("\ncmd:\n%s\n" % cmd)
|
4
|
127 run_command(cmd)
|
|
128 else:
|
|
129 ofh.write("\nMutation region BED not received.\n")
|
0
|
130 # Roll up potentially resistance conferring mutations.
|
|
131 for mutation_region, mutation_hits in amr_mutations.iteritems():
|
|
132 for mutation_idx, mutation_hit in mutation_hits.iterrows():
|
|
133 mutation_name = mutation_region + ' ' + mutation_hit['REF'] + '->' + mutation_hit['ALT']
|
|
134 amr_to_draw = amr_to_draw.append(pandas.Series([mutation_name, mutation_hit['DRUG']], name=amr_to_draw.shape[0], index=amr_to_draw.columns))
|
|
135
|
4
|
136 if amr_deletions_file not in [None, 'None'] and os.path.getsize(amr_deletions_file) > 0:
|
0
|
137 # Roll up deletions that might confer resistance.
|
3
|
138 try:
|
|
139 amr_deletions = pandas.read_csv(filepath_or_buffer=amr_deletions_file, sep='\t', header=None)
|
|
140 except Exception:
|
|
141 amr_deletions = pandas.DataFrame()
|
1
|
142 if amr_deletions.shape[0] > 0:
|
|
143 amr_deletions.columns = ['contig', 'start', 'stop', 'name', 'type', 'drug', 'note']
|
|
144 amr_deletions = amr_deletions.loc[amr_deletions['type'].isin(['large-deletion', 'any']), :]
|
|
145 for deletion_idx, deleted_gene in amr_deletions.iterrows():
|
|
146 amr_to_draw = amr_to_draw.append(pandas.Series(['\u0394' + deleted_gene[3], deleted_gene[5]], name=amr_to_draw.shape[0], index=amr_to_draw.columns))
|
0
|
147
|
|
148 if amr_to_draw.shape[0] > 1:
|
|
149 ofh.write("\nDrawing AMR matrix...\n")
|
|
150 present_genes = amr_to_draw['gene'].unique()
|
|
151 present_drugs = amr_to_draw['drug'].unique()
|
|
152 amr_matrix = pandas.DataFrame(0, index=present_genes, columns=present_drugs)
|
|
153 for hit_idx, hit in amr_to_draw.iterrows():
|
|
154 amr_matrix.loc[hit[0], hit[1]] = 1
|
7
|
155 amr_matrix_png = os.path.join(amr_matrix_png_dir, 'amr_matrix.png')
|
0
|
156 int_matrix = amr_matrix[amr_matrix.columns].astype(int)
|
|
157 figure, axis = pyplot.subplots()
|
6
|
158 heatmap = axis.pcolor(int_matrix, cmap=pyplot.cm.Blues, linewidth=0)
|
0
|
159 axis.invert_yaxis()
|
|
160 axis.set_yticks(numpy.arange(0.5, len(amr_matrix.index)), minor=False)
|
|
161 axis.set_yticklabels(int_matrix.index.values)
|
|
162 axis.set_xticks(numpy.arange(0.5, len(amr_matrix.columns)), minor=False)
|
|
163 axis.set_xticklabels(amr_matrix.columns.values, rotation=90)
|
|
164 axis.xaxis.tick_top()
|
|
165 axis.xaxis.set_label_position('top')
|
|
166 pyplot.tight_layout()
|
|
167 pyplot.savefig(amr_matrix_png, dpi=300)
|
|
168 else:
|
|
169 ofh.write("\nEmpty AMR matrix, nothing to draw...\n")
|
|
170 ofh.close()
|
|
171
|
|
172
|
|
173 if __name__ == '__main__':
|
|
174 parser = argparse.ArgumentParser()
|
|
175
|
1
|
176 parser.add_argument('--amr_feature_hits_dir', action='store', dest='amr_feature_hits_dir', help='Directory of tabular files containing feature hits')
|
|
177 parser.add_argument('--amr_deletions_file', action='store', dest='amr_deletions_file', default=None, help='AMR deletions BED file')
|
7
|
178 parser.add_argument('--varscan_vcf_file', action='store', dest='varscan_vcf_file', default=None, help='Varscan VCF file produced by the call_amr_mutations tool')
|
|
179 parser.add_argument('--amr_mutation_regions_bed_file', action='store', dest='amr_mutation_regions_bed_file', default=None, help='AMR mutation regions BED file')
|
0
|
180 parser.add_argument('--amr_gene_drug_file', action='store', dest='amr_gene_drug_file', help='AMR_gene_drugs tsv file')
|
4
|
181 parser.add_argument('--reference_genome', action='store', dest='reference_genome', help='Reference genome fasta file')
|
7
|
182 parser.add_argument('--mutation_regions_dir', action='store', dest='mutation_regions_dir', help='Directory for mutation regions TSV files produced by this tool')
|
|
183 parser.add_argument('--amr_matrix_png_dir', action='store', dest='amr_matrix_png_dir', help='Directory for PNG files produced by this tool')
|
0
|
184
|
|
185 args = parser.parse_args()
|
|
186
|
4
|
187 # Get the collection of feature hits files. The collection
|
0
|
188 # will be sorted alphabetically and will contain 2 files
|
|
189 # named something like AMR_CDS_311_2022_12_20.fasta and
|
|
190 # Incompatibility_Groups_2023_01_01.fasta.
|
1
|
191 amr_feature_hits_files = []
|
|
192 for file_name in sorted(os.listdir(args.amr_feature_hits_dir)):
|
|
193 file_path = os.path.abspath(os.path.join(args.amr_feature_hits_dir, file_name))
|
|
194 amr_feature_hits_files.append(file_path)
|
0
|
195
|
4
|
196 # Load the reference genome into memory.
|
|
197 reference = load_fasta(args.reference_genome)
|
|
198 reference_size = 0
|
|
199 for i in reference:
|
|
200 reference_size += len(i.seq)
|
|
201
|
7
|
202 draw_amr_matrix(amr_feature_hits_files, args.amr_deletions_file, args.varscan_vcf_file, args.amr_mutation_regions_bed_file, args.amr_gene_drug_file, reference, reference_size, args.mutation_regions_dir, args.amr_matrix_png_dir)
|