-
Notifications
You must be signed in to change notification settings - Fork 20
/
SNPsplit_genome_preparation
executable file
·1713 lines (1397 loc) · 62.1 KB
/
SNPsplit_genome_preparation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env perl
use warnings;
use strict;
use Getopt::Long;
use FindBin qw($Bin);
use lib "$Bin/../lib";
use Cwd;
## This program is Copyright (C) 2014-23, Felix Krueger (fkrueger@altoslabs.com)
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License, or
## (at your option) any later version.
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
## You should have received a copy of the GNU General Public License
## along with this program. If not, see <http://www.gnu.org/licenses/>.
### This script filters the latest VCF file for various SNPs versus the GRCm39 mouse genome build and writes high confidence SNPs into a folder called 'SNPs_Sanger';
### Update Dec 2022: The current version of the the Mouse Genomes Project (https://www.mousegenomes.org/) is v8. The SNP file may ne obtained here:
### https://ftp.ebi.ac.uk/pub/databases/mousegenomes/REL-2112-v8-SNPs_Indels/: "mgp_REL2021_snps.vcf.gz"
### Older versions of the SNPs/genomes are no longer supported but might still work (e.g.: 'mgp.v5.merged.snps_all.dbSNP142.vcf.gz'). However, please note that older
### files also use the, now outdated, genome build GRCm38!
# Modifying 20 December 2022 - to accept the new release v8 and work with the latest Mouse Genome Project
# https://ftp.ebi.ac.uk/pub/databases/mousegenomes/REL-2112-v8-SNPs_Indels/ "mgp_REL2021_snps.vcf.gz"
## Reading in a BAM or SAM file
my $pipeline_version = '0.6.0';
my $parent_dir = getcwd();
my ($vcf_file,$strain,$strain2,$strain_index,$strain2_index,$genome_folder,$skip_filtering,$nmasking,$full_sequence,$dual_hybrid,$genome_build,$v7) = process_commandline ();
my %snps; # storing all filtered SNPs
my %snps_dual_genome; # storing all SNPs for dual genome
my %homozygous_SNPs; # storing SNP genotypes and FILTER value for dual hybrids
my $snp_file_strain = "all_SNPs_${strain}_${genome_build}.txt.gz";
my $snp_file_strain2;
if ($strain2){
$snp_file_strain2 = "all_SNPs_${strain2}_${genome_build}.txt.gz";
}
else{
$snp_file_strain2 = 'irrelevant_for_single_hybrid_mode';
}
my $new_ref_snp_annoations; # this will store the new Ref/SNP annotations for dual hybrids
warn "Summarising SNPsplit Genome Preparation Parameters\n";
warn "="x50,"\n";
unless ($skip_filtering){
warn "Processing SNPs from VCF file:\t\t$vcf_file\n";
}
if ($skip_filtering){
warn "Reading/filtering VCF file:\t\tNo (skipped by user)\n";
}
else{
warn "Reading/filtering VCF file:\t\tYes (default)\n";
}
warn "Reference genome:\t\t\t$genome_folder\n";
# N-masking
if ($nmasking){
warn "N-masking:\t\t\t\tYes\n";
}
else{
warn "N-masking:\t\t\t\tNo\n";
}
# Full SNP incorporation
if ($full_sequence){
warn "Full SNP genome:\t\t\tYes\n";
}
else{
warn "Full SNP genome:\t\t\tNo\n";
}
warn "SNP strain:\t\t\t\t$strain\n";
if ($strain2){
warn "SNP strain 2:\t\t\t\t$strain2\n";
}
if ($dual_hybrid){
warn "Dual hybrid, new Ref/SNP:\t\t$strain/$strain2\n";
}
warn "\n";
### Dealing with chromosomes
my @chroms;
my %chroms; # here we will keep a record whether a chromosome had been covered with SNPs (this is dictated by the VCF file)
if ($skip_filtering){
# HUMAN GENOME @chroms = (1..22,'X','Y','MT'); # this is currently using chromosomes for the human genome
@chroms = (1..19,'X','Y','MT'); # MOUSE GENOME this is currently using chromosomes for the mouse genome
}
else{ # default
@chroms = detect_chroms();
}
# Keeping a record of chromosomes for which we have have SNP information available
foreach my $c (@chroms){
$chroms{$c} = 1;
}
if ($skip_filtering){
print "Using the following chromosomes (HARDCODED IN!!!):\n";
}
else{
print "Using the following chromosomes (detected from VCF file >>$vcf_file<<):\n";
}
print join ("\t",@chroms),"\n\n";
### Determining and Filtering homozygous high-confidence SNPs for the strain in question
if ($skip_filtering){
warn "Skipped reading the VCF file and filtering SNPs again (specified by user)\n\n";
}
else{
filter_relevant_SNP_calls_from_VCF($strain,$strain_index,'1'); # the last number is the strain identity, here the first strain
warn "Finished filtering and writing out SNPs for strain $strain\n\n";
}
### Storing the entire genome sequence
my %chromosomes; # genomic sequence
read_genome_into_memory($parent_dir);
### Create modified genome
my $new_n_total = 0;
my $new_snp_total = 0;
my $already_total = 0;
my $low_confidence = 0;
# Writing a genome generation report file
my $report = "${strain}_genome_preparation_report.txt";
open (REPORT,'>',$report) or die "Failed to write to file $report: $!\n";
for my $chr (sort keys %chromosomes) {
# If there SNPs associated with the current chromosome, modify the genomic sequence
# this may be N-masking, full sequence or both
if (exists $chroms{$chr} ){
# warn "Got SNP information for chromosome $chr. Creating modified chromosome\n";
create_modified_chromosome($chr,$strain);
}
else{
# warn "Got no SNP information for chromosome $chr. Printing sequence only...\n";
if ($nmasking){
write_SNP_chromosome($chr,$chromosomes{$chr},1,$strain);
}
if ($full_sequence){
write_SNP_chromosome($chr,$chromosomes{$chr},0,$strain);
}
}
}
if ($nmasking){
warn "\n\nSummary\n$new_n_total Ns were newly introduced into the N-masked genome for strain $strain in total\n";
print REPORT "\nSummary\n$new_n_total Ns were newly introduced into the N-masked genome for strain $strain in total\n";
}
if ($full_sequence){
warn "$new_snp_total SNPs were newly introduced into the full sequence genome version for strain $strain in total\n\n";
print REPORT "$new_snp_total SNPs were newly introduced into the full sequence genome version for strain $strain in total\n";
}
warn "\n";
close REPORT;
### Create modified genome 2
if ($dual_hybrid){
warn "Now starting to work on strain 2 [$strain2]\n";
# Need to read and filter the SNP file once more for Strain 2
### Determining and Filtering homozygous high-confidence SNPs for the strain in question
if ($skip_filtering){
warn "Skipped reading the VCF file and filtering SNPs again for strain 2 (specified by user)\n\n";
}
else{
filter_relevant_SNP_calls_from_VCF($strain2,$strain2_index,'2'); # the last number is the strain identity, here the second strain for dual hybrids
warn "Finished filtering and writing out SNPs for strain 2 [$strain2]\n\n";
}
$new_n_total = 0;
$new_snp_total = 0;
$already_total = 0;
$low_confidence = 0;
# Writing a genome generation report file for Strain 2
my $report = "${strain2}_genome_preparation_report.txt";
open (REPORT,'>',$report) or die "Failed to write to file $report: $!\n";
for my $chr (sort keys %chromosomes) {
if (exists $chroms{$chr} ){
# warn "Got SNP information for chromosome $chr. Creating modified chromosome\n";
create_modified_chromosome($chr,$strain2);
}
else{
# warn "Got no SNP information for chromosome $chr. Printing sequence only...\n";
if ($nmasking){
write_SNP_chromosome($chr,$chromosomes{$chr},1,$strain2);
}
if ($full_sequence){
write_SNP_chromosome($chr,$chromosomes{$chr},0,$strain2);
}
}
}
if ($nmasking){
warn "\nSummary\n$new_n_total Ns were newly introduced into the N-masked genome for strain 2 [$strain2] in total\n";
print REPORT "\nSummary\n$new_n_total Ns were newly introduced into the N-masked genome for strain 2 [$strain2] in total\n";
}
if ($full_sequence){
warn "$new_snp_total SNPs were newly introduced into the full sequence genome version for strain 2 [$strain2] in total\n\n";
print REPORT "$new_snp_total SNPs were newly introduced into the full sequence genome version for strain 2 [$strain2] in total\n";
}
close REPORT;
### Final dual genome contruction report
$report = "${strain}_${strain2}_dual_hybrid.genome_preparation_report.txt";
open (REPORT,'>',$report) or die "Failed to write to file $report: $!\n";
### Now just need to construct the dual hybrid genome
determine_SNPs_between_strain_and_strain2();
warn "done...\n";
### Resetting the genomic reference sequence
%chromosomes = (); # genomic sequence
warn "Changing the genomic reference sequence to the full sequence of strain $strain\n\n";
print REPORT "Changing the genomic reference sequence to the full sequence of strain $strain\n\n";
$genome_folder = "${parent_dir}/${strain}_full_sequence/";
read_genome_into_memory("${parent_dir}");
warn "Reading and storing all new SNPs with Ref/SNP: $strain/$strain2 from '$new_ref_snp_annoations'\n";
print REPORT "Reading and storing all new SNPs with Ref/SNP: $strain/$strain2 from '$new_ref_snp_annoations'\n";
read_new_snp_annotation($new_ref_snp_annoations,$strain,$strain2);
$new_n_total = 0;
$new_snp_total = 0;
$already_total = 0;
$low_confidence = 0;
#for my $chr (@chroms) {
# TODO: here we need to loop through %chroms and not @chroms
for my $chr (sort keys %chromosomes) {
if (exists $chroms{$chr} ){
warn "Got SNP information for chromosome $chr. Creating modified chromosome\n";
create_modified_chromosome_dual_hybrid($chr,$strain,$strain2);
}
else{
warn "Got no SNP information for chromosome $chr. Printing sequence only...\n";
if ($nmasking){
write_SNP_chromosome($chr,$chromosomes{$chr},1,"${strain}_${strain2}_dual_hybrid.based_on_${genome_build}");
}
if ($full_sequence){
write_SNP_chromosome($chr,$chromosomes{$chr},0,"${strain}_${strain2}_dual_hybrid.based_on_${genome_build}");
}
}
}
if ($nmasking){
warn "\nSummary\n$new_n_total Ns were newly introduced into the N-masked genome for strain/strain 2 [${strain}/$strain2] in total\n";
print REPORT "\nSummary\n$new_n_total Ns were newly introduced into the N-masked genome for strainstrain 2 [${strain}$strain2] in total\n";
}
if ($full_sequence){
warn "$new_snp_total SNPs were newly introduced into the full sequence genome version for strainstrain 2 [${strain}/$strain2] in total\n\n";
print REPORT "$new_snp_total SNPs were newly introduced into the full sequence genome version for strainstrain 2 [${strain}/$strain2] in total\n";
}
close REPORT;
}
warn "All done. Genome(s) are now ready to be indexed with your favourite aligner!\nFYI, aligners shown to work with SNPsplit are Bowtie2, STAR, HISAT2, HiCUP and Bismark (STAR and Hisat2 require disabling soft-clipping, please check the SNPsplit manual for details)\n\n";
#############################################################
### SUBROUTINES
#############################################################
sub read_new_snp_annotation {
my ($file,$strain,$strain2) = @_;
warn "Reading $strain/$strain2 SNPs from file '$file'\n"; sleep(1);
unless (-e $file) {
die "Couldn't find SNP file '$file'\n";
}
if ($file =~ /gz$/){
open (IN, "gunzip -c $file |") or die $!;
}
else{
open (IN,$file) or die $!;
}
my $count = 0;
while (<IN>) {
chomp;
$count++;
if ($count %1000000 == 0){
warn "Processed $count lines so far\n";
}
# warn "$_\n"; sleep(1);
my (undef,$chr,$pos,$strand,$allele) = split(/\t/);
unless ($allele){
warn "'$_'\n"; sleep(1);
}
# warn "$allele\n";
# ref here is the $strain sequence, SNP is the $strain2 sequence
my ($ref,$snp);
if ($allele =~ /^([GATC])\/([GATC])$/) {
$ref = $1;
$snp = $2;
}
else {
warn "Skipping allele '$allele'\n";
next;
}
if ($strand == -1) {
$ref =~ tr/GATC/CTAG/;
$snp =~ tr/GATC/CTAG/;
}
$snps_dual_genome{$chr} -> {$pos}-> {ref} = $ref;
$snps_dual_genome{$chr} -> {$pos}-> {snp} = $snp;
}
close IN or die;
}
sub determine_SNPs_between_strain_and_strain2{
warn "Determining new Ref [$strain] and SNP [$strain2] annotations\n";
warn "============================================================\n\n";
my $out_strain = "${strain}_specific_SNPs.${genome_build}.txt";
open (OUT_STRAIN,'>',$out_strain) or die $!;
warn "Writing $strain specific SNPs (relative to the $genome_build reference) to >>$out_strain<<\n";
my $out_strain2 = "${strain2}_specific_SNPs.${genome_build}.txt";
open (OUT_STRAIN2,'>',$out_strain2) or die $!;
warn "Writing $strain2 specific SNPs (relative to the $genome_build reference) to >>$out_strain2<<\n";
my $out_common = "${strain}_${strain2}_SNPs_in_common.${genome_build}.txt";
open (OUT_COMMON,'>',$out_common) or die $!;
warn "Writing SNPs in common between $strain and $strain2 (relative to the $genome_build reference) to >>$out_common<<\n";
my $all_strain_strain2 = "all_${strain2}_SNPs_${strain}_reference.based_on_${genome_build}.txt";
open (ALL_STRAIN_STRAIN2,'>',$all_strain_strain2) or die $!;
warn "Writing all new SNPs >>$strain/$strain2 to >>$all_strain_strain2<<\n\n";
$new_ref_snp_annoations = $all_strain_strain2; # required for N-masking etc.
read_snp_files();
}
sub read_snp_files{
unless (-e "$snp_file_strain"){
die "Expected SNP file [$snp_file_strain] for strain $strain did not exist! Please make sure that it is present in the current working directory\n\n";
}
unless (-e "$snp_file_strain2"){
die "Expected SNP file 2 [$snp_file_strain2}] for strain $strain2 did not exist! Please make sure that it is present in the current working directory\n\n";
}
if ($snp_file_strain =~ /\.gz$/){
open (SNP_STRAIN,"gunzip -c $snp_file_strain |") or die "Failed to read from gzipped file $snp_file_strain: $!\n\n";
}
else{
open (SNP_STRAIN,"$snp_file_strain") or die "Failed to read from $snp_file_strain: $!\n\n";
}
if ($snp_file_strain2 =~ /\.gz$/){
open (SNP_STRAIN2,"gunzip -c $snp_file_strain2 |") or die "Failed to read from gzipped file $snp_file_strain2: $!\n\n";
}
else{
open (SNP_STRAIN2,"$snp_file_strain2") or die "Failed to read from file $snp_file_strain2: $!\n\n";
}
### READING FROM SNP FILE FOR STRAIN 1 (these are only high confidence SNPs)
warn "Storing SNP positions for strain $strain provided in '$snp_file_strain'\n";
sleep (1);
my $snp_count_strain = 0;
while (<SNP_STRAIN>){
++$snp_count_strain;
chomp;
my ($chr,$pos,$diff) = (split /\t/)[1,2,4];
my ($ref,$snp) = (split /\//,$diff);
# warn "$chr\t$pos\tRef: $ref\tSNP: $snp\n"; sleep(1);
$snps{$chr}->{$pos}->{ref} = $ref;
$snps{$chr}->{$pos}->{snp} = $snp;
$snps{$chr}->{$pos}->{read} = 0;
}
warn "Stored $snp_count_strain positions in total\n\n";
### READING FROM SNP FILE FOR STRAIN 2
warn "Now reading and comparing SNP positions for strain $strain2 provided in '$snp_file_strain2'\n";
my $snp_count_strain2 = 0;
my $same = 0;
my $different = 0;
my $unique_ref = 0; # unique ref here means the new Reference, i.e. Strain
my $unique_SNP = 0; # unique SNP here means the new SNP genome, i.e. Strain2
my $confidence_discrepancy = 0; # a measure for how many times a SNP was found as homozygous in both strains but with low confidence in one of them
while (<SNP_STRAIN2>){
++$snp_count_strain2;
chomp;
my ($chr,$pos,$diff) = (split /\t/)[1,2,4];
my $location = join (':',$chr,$pos);
my ($ref,$snp) = (split /\//,$diff);
# warn "$chr\t$pos\tRef: $ref\tSNP: $snp\n"; sleep(1);
if (exists $snps{$chr}->{$pos} ){
$snps{$chr}->{$pos}->{read}++; # SNP is present in both genomes as high confidence SNP.
unless ($ref eq $snps{$chr}->{$pos}->{ref}){
warn "reference was different for the same position!!!\n";
}
# The SNP compared to the GRCm39 genome is the same in SNP=Strain2 ($snp) and Ref=Strain ($snps{$chr}->{$pos}->{snp})
if ($snp eq $snps{$chr}->{$pos}->{snp}){
++$same;
print OUT_COMMON "$_\n";
# warn "SNP is the same in Ref and SNP. Printing to SNPs in common\n";
next;
}
else{
++$different;
# warn "GRCm39 sequence:\t\t$ref\n";
# warn "Strain (=new Ref) sequence:\t$snps{$chr}->{$pos}->{snp}\n";
# warn "SNP (=new SNP) sequence:\t\t$snp\n";
# sleep(1);
### we need a new SNP format where Ref/SNP is now Strain/Strain2
my $new_snp = "$snps{$chr}->{$pos}->{snp}/$snp";
# warn "New $strain/$strain2 SNP is: $new_snp\n";
# sleep (1);
if ($new_snp){
print ALL_STRAIN_STRAIN2 "$different\t$chr\t$pos\t1\t$new_snp\n";
}
else{
warn "'$new_snp' is empty, skipping\n";
}
}
}
else{
# Now we need to check whether the SNP was also present but failing the filter in Strain 1
if (exists $homozygous_SNPs{$location}->{strain1_filter}){
# warn "Strain 1: $location\t$homozygous_SNPs{$location}->{strain1_genotype}\t$homozygous_SNPs{$location}->{strain1_filter}\n";
# warn "Strain 2: $location\t$homozygous_SNPs{$location}->{strain2_genotype}\t$homozygous_SNPs{$location}->{strain2_filter}\n";
if ($homozygous_SNPs{$location}->{strain1_filter} eq 1){
# warn "Fine, positions was high confidence\n";
}
else{ # if the position failed the filter we move on irrespective of what the genotype was
++$confidence_discrepancy;
# warn "Strain 1: $location\t$homozygous_SNPs{$location}->{strain1_genotype}\t$homozygous_SNPs{$location}->{strain1_filter}\n";
# warn "Strain 2: $location\t$homozygous_SNPs{$location}->{strain2_genotype}\t$homozygous_SNPs{$location}->{strain2_filter}\n";
# warn "Confidence in Strain 1 SNP call was low, skipping this position irrespective of genotype\n\n";
next;
}
# warn "\n";
}
else{
# warn "SNP did not exist in hash\n";
}
++$unique_SNP;
# warn "SNP is unique to Strain2. Printing...\n";
# warn "$genome_strain sequence: $ref\n";
# warn "SNP (=Strain2) sequence: $snp\n"; sleep(1);
print OUT_STRAIN2 "$_\n"; # Strain has the same sequence as Black6 ($genome_build)
if ($_){
print ALL_STRAIN_STRAIN2 "$_\n";
}
else{
warn "'$_' is empty, skipping\n";
}
}
# last if ($snp_count_strain2 == 10000);
}
warn "Finally, looking at new reference [$strain] specific reads...\n"; sleep (1);
foreach my $chr (keys %snps){
foreach my $pos (keys %{$snps{$chr}}){
my $location = join (':',$chr,$pos);
if ($snps{$chr}->{$pos}->{read} == 0){ # present only for Strain 1
# Now we need to check whether the SNP was also present but failing the filter in Strain 2
if (exists $homozygous_SNPs{$location}->{strain2_filter}){
if ($homozygous_SNPs{$location}->{strain2_filter} eq 1){
# warn "Strain 1: $location\t$homozygous_SNPs{$location}->{strain1_genotype}\t$homozygous_SNPs{$location}->{strain1_filter}\n";
# warn "Strain 2: $location\t$homozygous_SNPs{$location}->{strain2_genotype}\t$homozygous_SNPs{$location}->{strain2_filter}\n";
# warn "Fine, genotype call was good in both strains\n";
}
else{
# warn "Strain 1: $location\t$homozygous_SNPs{$location}->{strain1_genotype}\t$homozygous_SNPs{$location}->{strain1_filter}\n";
# warn "Strain 2: $location\t$homozygous_SNPs{$location}->{strain2_genotype}\t$homozygous_SNPs{$location}->{strain2_filter}\n";
# warn "Confidence in Strain 2 SNP call was low, skipping this position irrespective of genotype\n\n";
++$confidence_discrepancy;
next;
}
}
++$unique_ref;
### here we need to use the SNP position (i.e. Strain sequence) as new reference and the GRCm39 sequence as the SNP (i.e. where Strain2 is the same as GRCm39)
print OUT_STRAIN "$chr\t$pos\t$snps{$chr}->{$pos}->{snp}/$snps{$chr}->{$pos}->{ref}\n"; # writing out an annotation track Strain vs GRCm39
### here we need to use the SNP position (i.e. Strain sequence) as new reference and the GRCm39 sequence as the SNP (i.e. where Strain2 is the same as GRCm39)
print ALL_STRAIN_STRAIN2 "${strain}_${unique_ref}\t$chr\t$pos\t1\t$snps{$chr}->{$pos}->{snp}/$snps{$chr}->{$pos}->{ref}\n";
}
if ($snps{$chr}->{$pos}->{read} >= 2){
die "SNP was present at least twice: $chr\t$pos\tcount: $snps{$chr}->{$pos}->{snp}\n\n";
}
}
}
warn "\nLooked at positions from new Reference strain [$strain]:\t\t$snp_count_strain\n";
warn "Compared positions from new SNP strain [$strain2]:\t\t$snp_count_strain2\n";
warn "======================================================\n";
warn "SNPs were the same in Ref and SNP genome (not written out):\t$same\n";
warn "SNPs were present in both Ref and SNP genome but had a different sequence:\t$different\n";
warn "SNPs were low confidence in one strain and thus ignored:\t$confidence_discrepancy\n";
warn "SNPs were unique to Ref [$strain]:\t\t\t\t$unique_ref\n";
warn "SNPs were unique to SNP [$strain2]:\t\t\t\t$unique_SNP\n\n";
print REPORT "Looked at positions from new Reference strain [$strain]:\t\t$snp_count_strain\n";
print REPORT "Compared positions from new SNP strain [$strain2]:\t\t$snp_count_strain2\n";
print REPORT "======================================================\n";
print REPORT "SNPs were the same in Ref and SNP genome (not written out):\t$same\n";
print REPORT "SNPs were present in both Ref and SNP genome but had a different sequence:\t$different\n";
print REPORT "SNPs were low confidence in one strain and thus ignored:\t$confidence_discrepancy\n";
print REPORT "SNPs were unique to Ref [$strain]:\t\t\t\t$unique_ref\n";
print REPORT "SNPs were unique to SNP [$strain2]:\t\t\t\t$unique_SNP\n\n";
close OUT_STRAIN;
close OUT_STRAIN2;
close OUT_COMMON;
close ALL_STRAIN_STRAIN2;
}
sub create_modified_chromosome {
my ($chr,$strain) = @_; # $strain may be strain 1 or 2
warn "Processing chromosome $chr (for strain $strain)\n";
unless ($chromosomes{$chr}){
warn "\nThe chromosome name given in the VCF file was '$chr' and was not found in the reference genome.\nA rather common mistake might be that the VCF file was downloaded from Ensembl (who use chromosome names such as 1, 2, X, MT)\nbut the genome from UCSC (who use chromosome names such as chr1, chr2, chrX, chrM)\n";
warn "The chromosome names in the reference genome folder were:\n";
foreach my $c (sort keys %chromosomes){
warn "$c\n";
}
die "[FATAL ERROR] Please ensure that the same version of the genome is used for both VCF annotations and reference genome (FastA files). Exiting...\n\n";
}
my $sequence = $chromosomes{$chr};
my $n_sequence;
if ($nmasking){
$n_sequence = $sequence;
}
my @snps = @{read_snps($chr,$strain)};
unless (@snps){
@snps = ();
warn "Clearing SNP array...\n"
}
my $count = 0;
my $lastPos = 0;
my $already = 0;
my $warn = 0;
my $new_n = 0;
my $new_snp = 0;
foreach my $snp (@snps) {
# Apply the SNP
++$count;
# warn "$snp->[0]\t$snp->[1]/$snp->[2]\n";
if ($snp->[0] == $lastPos) {
# Duplicate SNP
next;
}
$lastPos = $snp->[0];
# Check if the reference base is the same as the SNP base
if (substr ($sequence,$snp->[0]-1,1) eq $snp->[2]) {
# warn "Skipping $snp->[0] $snp->[1]/$snp->[2] since the ref and SNP base are the same\n";
++$already;
next;
}
# Check the reference base is correct
if (substr ($sequence,$snp->[0]-1,1) ne $snp->[1]) {
# warn "Skipping $snp->[0] $snp->[1]/$snp->[2] since the reference base didn't match\n";
$warn++;
next;
}
### Ref/Alt bases are matching, so we can proceed to changing the ref base for the SNP base or Ns (N-masking)
### N-masking
if ($nmasking){ # default
my $return = substr($n_sequence,($snp->[0])-1,1,'N'); # Replacing the base with 'N'
unless ($return){
warn "Replacing failed...\n";
}
++$new_n;
}
if ($full_sequence){
my $return = substr($sequence,$snp->[0]-1,1,$snp->[2]); # Replacing the reference with the SNP base
unless ($return){
warn "Replacing failed...\n";
}
++$new_snp;
}
}
$new_n_total += $new_n;
$new_snp_total += $new_snp;
$already_total += $already;
if ($nmasking){
write_SNP_chromosome($chr,$n_sequence,1,$strain);
}
if ($full_sequence){
write_SNP_chromosome($chr,$sequence,0,$strain);
}
warn "$count SNPs total for chromosome $chr\n";
if ($nmasking){ # default
warn "$new_n positions on chromosome $chr were changed to 'N'\n";
print REPORT "$new_n positions on chromosome $chr were changed to 'N'\n";
}
if ($full_sequence){
warn "$new_snp reference positions on chromosome $chr were changed to the SNP alternative base\n\n";
print REPORT "$new_snp reference positions on chromosome $chr were changed to the SNP alternative base\n\n";
}
warn "\n";
}
sub create_modified_chromosome_dual_hybrid {
my ($chr,$strain,$strain2) = @_;
warn "Processing chr$chr (to create new genome for $strain/$strain2)\n";
my $sequence = $chromosomes{$chr};
my $n_sequence;
if ($nmasking){
$n_sequence = $sequence;
}
my $count = 0;
my $lastPos = 0;
my $already = 0;
my $warn = 0;
my $new_n = 0;
my $new_snp = 0;
foreach my $pos (keys %{$snps_dual_genome{$chr}}) {
# Apply the SNP
++$count;
if ($pos == $lastPos) {
# Duplicate SNP
next;
}
$lastPos = $pos;
# Check if the reference base is the same as the SNP base
if (substr ($sequence,$pos-1,1) eq $snps_dual_genome{$chr}->{$pos}->{snp}) {
# warn "Skipping $pos $snps_dual_genome{$chr}->{$pos}->{ref}/$snps_dual_genome{$chr}->{$pos}->{snp} since the ref and SNP base are the same\n";
++$already;
next;
}
# Check the reference base is correct
if (substr ($sequence,$pos-1,1) ne $snps_dual_genome{$chr}->{$pos}->{ref}) {
# warn "Skipping $pos $snps_dual_genome{$chr}->{$pos}->{ref}/$snps_dual_genome{$chr}->{$pos}->{snp} since the reference base didn't match\n";
$warn++;
next;
}
### Ref/Alt bases are matching, so we can proceed to changing the ref base for the SNP base or Ns (N-masking)
### N-masking
if ($nmasking){ # default
my $return = substr($n_sequence,$pos-1,1,'N'); # Replacing the base with 'N'
unless ($return){
warn "Replacing failed...\n";
}
++$new_n;
}
if ($full_sequence){
my $return = substr($sequence,$pos-1,1,$snps_dual_genome{$chr}->{$pos}->{snp}); # Replacing the reference with the SNP base
unless ($return){
warn "Replacing failed...\n";
}
++$new_snp;
}
}
$new_n_total += $new_n;
$new_snp_total += $new_snp;
$already_total += $already;
if ($nmasking){
write_SNP_chromosome($chr,$n_sequence,1,"${strain}_${strain2}_dual_hybrid.based_on_${genome_build}");
}
if ($full_sequence){
write_SNP_chromosome($chr,$sequence,0,"${strain}_${strain2}_dual_hybrid.based_on_${genome_build}");
}
warn "$count SNPs total for chromosome $chr\n";
if ($nmasking){ # default
warn "$new_n positions on chromosome $chr were changed to 'N'\n";
print REPORT "$new_n positions on chromosome $chr were changed to 'N'\n";
}
if ($full_sequence){
warn "$new_snp reference positions on chromosome $chr were changed to the SNP alternative base\n\n";
print REPORT "$new_snp reference positions on chromosome $chr were changed to the SNP alternative base\n\n";
}
warn "\n";
}
sub write_SNP_chromosome {
my ($chr,$sequence,$nm,$strain) = @_; # $nm will discriminate between N-masking and full sequence output
if ($nm){
warn "Writing modified chromosome (N-masking)\n";
}
else{
warn "Writing modified chromosome (incorporating SNPs)\n";
}
my $type;
my $outfile;
if ($nm){
$type = 'N-masked';
$outfile = "chr${chr}.N-masked.fa";
}
if ($nm == 0){
$type = 'full_sequence';
$outfile = "chr${chr}.SNPs_introduced.fa";
}
# warn "Starting sequence is ".length($sequence)." bp\n";
if ($nm){
warn "Writing N-masked output to: ${parent_dir}/${strain}_${type}/$outfile\n";
unless (-d "${parent_dir}/${strain}_${type}/"){ # creating the output directory if required
mkdir "${parent_dir}/${strain}_${type}/";
}
open (OUT,'>',"${parent_dir}/${strain}_${type}/${outfile}") or die "Failed to write to file ${parent_dir}/${strain}_${type}/${outfile}: $!\n\n";
print OUT ">$chr\n";
}
elsif ($nm == 0){
warn "Writing full sequence output to: ${parent_dir}/${strain}_${type}/$outfile\n";
unless (-d "${parent_dir}/${strain}_${type}/"){ # creating the output directory if required
mkdir "${parent_dir}/${strain}_${type}/";
}
open (OUT,'>',"${parent_dir}/${strain}_${type}/${outfile}") or die "Failed to write to file ${parent_dir}/${strain}_${type}/${outfile}: $!\n\n";
print OUT ">$chr\n";
}
else{
warn "Running out of options...\n\n";
}
my $pos = 0;
# Writing out chromosome files with 100 characters per line
while ($pos < length($sequence)-100) {
print OUT substr($sequence,$pos,100),"\n";
$pos += 100;
}
print OUT substr($sequence,$pos),"\n"; # rest
close OUT or die $!;
}
sub read_snps {
my ($chr,$strain) = @_;
my @snps = ();
my $file = "${parent_dir}/SNPs_${strain}/chr$chr.txt";
### If the SNP folder doesn't exist we can be certain that something is going wrong
unless (-d "${parent_dir}/SNPs_${strain}"){
die "Folder >>${parent_dir}/SNPs_${strain}<< doesn't exist. Try losing the option --skip_filtering to generate the folder and SNP files from the VCF file\n\n";
}
### not sure but I think for some chromosomes there might not be any SNP files, e.g. chr MT or chrY. In this case the sequence is written out again unmodified
unless (-e $file) {
warn "Couldn't find SNP file for chromosome '$chr' '$file' didn't exist. Skipping...\n";
return \@snps;
}
warn "Reading SNPs from file $file\n";
open (IN,$file) or die $!;
while (<IN>) {
$_ =~ s/\r//; # Windows line endings...
chomp;
# warn "$_\n"; sleep(1);
next unless ($_);
my (undef,undef,$pos,$strand,$allele) = split(/\t/);
# warn "$pos , $strand , $allele\n";
next unless ($allele);
my ($ref_allele,$snp_allele);
if ($allele =~ /^([GATC])\/([GATC])$/) {
$ref_allele = $1;
$snp_allele = $2;
}
else {
warn "Skipping allele '$allele' as it appears to contain non DNA bases (only G,A,T,C allowed)\n";
next;
}
if ($strand == -1) { # if the strand is given as -1 it means that the SNP is on the reverse strand and thus needs reverse-coplementing
$ref_allele =~ tr/GATC/CTAG/;
$snp_allele =~ tr/GATC/CTAG/;
}
# warn "$pos , $ref_allele , $snp_allele\n"; sleep(1);
push @snps,[$pos,$ref_allele,$snp_allele];
}
# sorting snps
@snps = sort {$a->[0] <=> $b->[0]} @snps;
return \@snps;
close IN or warn "Failed to close filehandle IN for file $file: $!\n\n";
}
###
sub filter_relevant_SNP_calls_from_VCF{
my ($strain, $strain_index, $strain_identity) = @_;
if ($vcf_file =~ /gz$/){
open (IN,"gunzip -c $vcf_file |") or die "Failed to open file '$vcf_file': $!\n";
}
else{
open (IN, $vcf_file) or die "Failed to read Input VCF file '$vcf_file': $!\n";
}
my %all_SNPs; # storing filtered SNPs
my $count = 0;
my $other = 0;
my $too_many = 0;
my %fhs;
my $hcg_count = 0;
my $low_confidence = 0;
my $same = 0;
my $homozygous = 0;
my $indel_pos = 0;
my $format_index; # required to get extract entries from FORMAT field
my $info_index; # required to look at INFO field, e.g. for INDELs
my $gt_index; # required to get GENOTYPE
my $fi_index; # required to get FILTER value
my $dir = "SNPs_$strain";
unless (-d $dir){
warn "Folder '$dir' doesn't exist. Creating it for you...\n\n";
mkdir $dir or die "Failed to created directory $dir\n: $!\n\n";
}
# Opening filehandles for the SNP files
for my $chr (@chroms) {
my $filename = "SNPs_$strain/chr".$chr.'.txt';
open (my $fh,'>',$filename) or die "Couldn't open filehandle $!\n";
$fhs{$chr} = $fh;
print {$fhs{$chr}} ">$chr\n";
}
while (<IN>){
$_ =~ s/(\r|\n)//g; # removing end of line characters
# warn "$_\n"; sleep(1);
next if ($_ =~ /^\#\#/); # filters out header information lines
if ($_ =~ /^\#CHROM/){ # Table Header
my ($name) = (split /\t/)[$strain_index];
warn "Analysing SNP fields for name >$name<\n";
my @format_fields = split /\t/;
my $field_index = 0;
foreach my $field (@format_fields){
# warn "$field_index\t$field\n";#
if ($field eq "FORMAT"){#
$format_index = $field_index;
}
if ($field eq "INFO"){#
$info_index = $field_index;
}
$field_index++;
}
if (defined $format_index){
warn "Using FORMAT field index: $format_index\n";
}
else{
die "Failed to extract index of field 'FORMAT'. Hmmm...";
}
if (defined $info_index){
warn "Using INFO field index: $info_index\n";
}
else{
die "Failed to extract index of field 'INFO'. Hmmm...";
}
next;
}
$count++;
if ($count%1000000 ==0){
warn "processed $count lines\n";
}
# warn "$_\n"; sleep(1);
# last if ($count == 10000);
my ($chr,$pos,$ref,$alt,$info,$format,$strain) = (split /\t/)[0,1,3,4,$info_index,$format_index,$strain_index];
# warn "$chr , $pos , $ref , $alt , $info , $format, $strain\n"; sleep(1);
# 06 April 2021: adapting for variable VCF format
unless (defined $gt_index){ # only needed once
warn "GT index not defined, checking...\n";
my %strain_hash;
my @keys;
my @values;
my $i;
@keys = split/:/,$format;
# warn "Number of elements in keys:", scalar @keys;
for( $i = 0; $i < scalar @keys; $i++){
$strain_hash{$keys[$i]} = $i;
}
# foreach my $k (keys %strain_hash){
# warn "$k\t$strain_hash{$k}\n";
# }
if(exists($strain_hash{'GT'})){
$gt_index = $strain_hash{'GT'};
warn "Setting GT index to >>$strain_hash{'GT'}<<\n";
}