From 86e99c8826b2b62ac254e15d27abf420b322e6c5 Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Mon, 13 Jan 2020 10:12:20 +0000 Subject: [PATCH 01/60] Apply fix provided Bt Kai Ye/Xiaofei Yang to recover missed complex even (known to be real) --- c++/pindel.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/c++/pindel.cpp b/c++/pindel.cpp index b7bceee..a7930b7 100644 --- a/c++/pindel.cpp +++ b/c++/pindel.cpp @@ -1216,7 +1216,7 @@ if (Analyze_TD_INV_LI_Others) { } { - if (Reads[ReadIndex].IndelSize >= MIN_IndelSize_NT && Reads[ReadIndex].IndelSize > Reads[ReadIndex].NT_size) + if (Reads[ReadIndex].IndelSize >= MIN_IndelSize_NT) { DI[Reads[ReadIndex].BPLeft / BoxSize].push_back(ReadIndex); Reads[ReadIndex].Used = true; @@ -1274,7 +1274,7 @@ if (Analyze_TD_INV_LI_Others) { } { - if (Reads[ReadIndex].IndelSize >= MIN_IndelSize_NT && Reads[ReadIndex].IndelSize > Reads[ReadIndex].NT_size) + if (Reads[ReadIndex].IndelSize >= MIN_IndelSize_NT) { DI[Reads[ReadIndex].BPLeft / BoxSize].push_back(ReadIndex); Reads[ReadIndex].Used = true; @@ -2266,14 +2266,14 @@ void CheckLeft_Close(const SPLIT_READ & OneRead, for (short i = 0; i <= OneRead.MAX_SNP_ERROR; i++) { if (Left_PD[i].size() == 1 && CurrentLength >= BP_Left_Start + i) { Sum = 0; - if (ADDITIONAL_MISMATCH) + if (ADDITIONAL_MISMATCH) for (short j = 0; j <= i+ADDITIONAL_MISMATCH; j++) Sum += Left_PD[j].size(); //Maybe the previous SNPS still exist; revise in 2018.11.26 by Liang Hao - if (Sum == 1 && i <= (short)(CurrentLength * Seq_Error_Rate + 1)) { + if (Sum == 1 && i <= (short)(CurrentLength * Seq_Error_Rate + 1)) { UniquePoint TempOne; TempOne.LengthStr = CurrentLength; TempOne.AbsLoc = Left_PD[i][0]; - + TempOne.Direction = FORWARD; TempOne.Strand = ANTISENSE; TempOne.Mismatches = i; @@ -2356,7 +2356,7 @@ void CheckRight_Close(const SPLIT_READ & OneRead, for (short i = 0; i <= OneRead.MAX_SNP_ERROR; i++) { if (Right_PD[i].size() == 1 && CurrentLength >= BP_Right_Start + i) { Sum = 0; - if (ADDITIONAL_MISMATCH) + if (ADDITIONAL_MISMATCH) for (short j = 0; j <= i+ADDITIONAL_MISMATCH; j++) Sum += Right_PD[j].size(); //Maybe the previous SNPS still exist; revise in 2018.11.26 by Liang Hao if (Sum == 1 && i <= (short)(CurrentLength * Seq_Error_Rate + 1)) { @@ -2446,10 +2446,10 @@ void CheckLeft_Far(const SPLIT_READ & OneRead, for (short i = 0; i <= OneRead.MAX_SNP_ERROR; i++) { if (Left_PD[i].size() == 1 && CurrentLength >= BP_Left_Start + i) { Sum = 0; - if (ADDITIONAL_MISMATCH) + if (ADDITIONAL_MISMATCH) for (short j = 0; j <= i+ADDITIONAL_MISMATCH; j++) Sum += Left_PD[j].size(); //Maybe the previous SNPS still exist; revise in 2018.11.26 by Liang Hao - if (Sum == 1 && i <= (short)(CurrentLength * Seq_Error_Rate + 1)) { + if (Sum == 1 && i <= (short)(CurrentLength * Seq_Error_Rate + 1)) { UniquePoint TempOne; TempOne.LengthStr = CurrentLength; TempOne.AbsLoc = Left_PD[i][0]; @@ -2565,7 +2565,7 @@ void CheckRight_Far(const SPLIT_READ & OneRead, for (short i = 0; i <= OneRead.MAX_SNP_ERROR; i++) { if (Right_PD[i].size() == 1 && CurrentLength >= BP_Right_Start + i) { Sum = 0; - if (ADDITIONAL_MISMATCH) + if (ADDITIONAL_MISMATCH) for (short j = 0; j <= i+ADDITIONAL_MISMATCH; j++) Sum += Right_PD[j].size(); //Maybe the previous SNPS still exist; revise in 2018.11.26 by Liang Hao if (Sum == 1 && i <= (short)(CurrentLength * Seq_Error_Rate + 1)) { @@ -4648,7 +4648,7 @@ void GetFarEnd_SingleStrandDownStreamInsertions(const string & CurrentChr, SPLIT End = Temp_One_Read.UP_Close[0].AbsLoc + Temp_One_Read.UP_Close[0].LengthStr; if (End > SpacerBeforeAfter + Temp_One_Read.InsertSize * 2 + DSizeArray[RangeIndex]) - Start = End - DSizeArray[RangeIndex] - Temp_One_Read.InsertSize * 2; + Start = End - DSizeArray[RangeIndex] - Temp_One_Read.InsertSize * 2; else Start = SpacerBeforeAfter; From d5726b428f2ed6da9d86a8afeca3867f2a5874c6 Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Mon, 13 Jan 2020 10:12:20 +0000 Subject: [PATCH 02/60] Apply fix provided Bt Kai Ye/Xiaofei Yang to recover missed complex even (known to be real) --- c++/pindel.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/c++/pindel.cpp b/c++/pindel.cpp index b7bceee..a7930b7 100644 --- a/c++/pindel.cpp +++ b/c++/pindel.cpp @@ -1216,7 +1216,7 @@ if (Analyze_TD_INV_LI_Others) { } { - if (Reads[ReadIndex].IndelSize >= MIN_IndelSize_NT && Reads[ReadIndex].IndelSize > Reads[ReadIndex].NT_size) + if (Reads[ReadIndex].IndelSize >= MIN_IndelSize_NT) { DI[Reads[ReadIndex].BPLeft / BoxSize].push_back(ReadIndex); Reads[ReadIndex].Used = true; @@ -1274,7 +1274,7 @@ if (Analyze_TD_INV_LI_Others) { } { - if (Reads[ReadIndex].IndelSize >= MIN_IndelSize_NT && Reads[ReadIndex].IndelSize > Reads[ReadIndex].NT_size) + if (Reads[ReadIndex].IndelSize >= MIN_IndelSize_NT) { DI[Reads[ReadIndex].BPLeft / BoxSize].push_back(ReadIndex); Reads[ReadIndex].Used = true; @@ -2266,14 +2266,14 @@ void CheckLeft_Close(const SPLIT_READ & OneRead, for (short i = 0; i <= OneRead.MAX_SNP_ERROR; i++) { if (Left_PD[i].size() == 1 && CurrentLength >= BP_Left_Start + i) { Sum = 0; - if (ADDITIONAL_MISMATCH) + if (ADDITIONAL_MISMATCH) for (short j = 0; j <= i+ADDITIONAL_MISMATCH; j++) Sum += Left_PD[j].size(); //Maybe the previous SNPS still exist; revise in 2018.11.26 by Liang Hao - if (Sum == 1 && i <= (short)(CurrentLength * Seq_Error_Rate + 1)) { + if (Sum == 1 && i <= (short)(CurrentLength * Seq_Error_Rate + 1)) { UniquePoint TempOne; TempOne.LengthStr = CurrentLength; TempOne.AbsLoc = Left_PD[i][0]; - + TempOne.Direction = FORWARD; TempOne.Strand = ANTISENSE; TempOne.Mismatches = i; @@ -2356,7 +2356,7 @@ void CheckRight_Close(const SPLIT_READ & OneRead, for (short i = 0; i <= OneRead.MAX_SNP_ERROR; i++) { if (Right_PD[i].size() == 1 && CurrentLength >= BP_Right_Start + i) { Sum = 0; - if (ADDITIONAL_MISMATCH) + if (ADDITIONAL_MISMATCH) for (short j = 0; j <= i+ADDITIONAL_MISMATCH; j++) Sum += Right_PD[j].size(); //Maybe the previous SNPS still exist; revise in 2018.11.26 by Liang Hao if (Sum == 1 && i <= (short)(CurrentLength * Seq_Error_Rate + 1)) { @@ -2446,10 +2446,10 @@ void CheckLeft_Far(const SPLIT_READ & OneRead, for (short i = 0; i <= OneRead.MAX_SNP_ERROR; i++) { if (Left_PD[i].size() == 1 && CurrentLength >= BP_Left_Start + i) { Sum = 0; - if (ADDITIONAL_MISMATCH) + if (ADDITIONAL_MISMATCH) for (short j = 0; j <= i+ADDITIONAL_MISMATCH; j++) Sum += Left_PD[j].size(); //Maybe the previous SNPS still exist; revise in 2018.11.26 by Liang Hao - if (Sum == 1 && i <= (short)(CurrentLength * Seq_Error_Rate + 1)) { + if (Sum == 1 && i <= (short)(CurrentLength * Seq_Error_Rate + 1)) { UniquePoint TempOne; TempOne.LengthStr = CurrentLength; TempOne.AbsLoc = Left_PD[i][0]; @@ -2565,7 +2565,7 @@ void CheckRight_Far(const SPLIT_READ & OneRead, for (short i = 0; i <= OneRead.MAX_SNP_ERROR; i++) { if (Right_PD[i].size() == 1 && CurrentLength >= BP_Right_Start + i) { Sum = 0; - if (ADDITIONAL_MISMATCH) + if (ADDITIONAL_MISMATCH) for (short j = 0; j <= i+ADDITIONAL_MISMATCH; j++) Sum += Right_PD[j].size(); //Maybe the previous SNPS still exist; revise in 2018.11.26 by Liang Hao if (Sum == 1 && i <= (short)(CurrentLength * Seq_Error_Rate + 1)) { @@ -4648,7 +4648,7 @@ void GetFarEnd_SingleStrandDownStreamInsertions(const string & CurrentChr, SPLIT End = Temp_One_Read.UP_Close[0].AbsLoc + Temp_One_Read.UP_Close[0].LengthStr; if (End > SpacerBeforeAfter + Temp_One_Read.InsertSize * 2 + DSizeArray[RangeIndex]) - Start = End - DSizeArray[RangeIndex] - Temp_One_Read.InsertSize * 2; + Start = End - DSizeArray[RangeIndex] - Temp_One_Read.InsertSize * 2; else Start = SpacerBeforeAfter; From a2a1bcdedbe3085dc36d1029b0223516be2c12c9 Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Mon, 20 Apr 2020 12:04:39 +0000 Subject: [PATCH 03/60] deletions working, cleaned up install process --- Dockerfile | 14 +- build/opt-build-local.sh | 2 + build/opt-build.sh | 3 + perl/Makefile.PL | 5 +- perl/bin/pindel.pl | 123 +---- perl/bin/pindelCohort.pl | 192 +++++++ perl/bin/pindelCohort_to_vcf.pl | 317 ++++++++++++ perl/lib/Sanger/CGP/Pindel/Implement.pm | 180 ++++++- perl/lib/Sanger/CGP/Pindel/InputGen/Pair.pm | 11 +- perl/lib/Sanger/CGP/Pindel/InputGen/Read.pm | 4 +- .../CGP/Pindel/OutputGen/PindelRecord.pm | 52 +- .../Pindel/OutputGen/PindelRecordParser.pm | 138 ++--- .../Pindel/OutputGen/VcfCohortConverter.pm | 486 ++++++++++++++++++ perl/t/outputGenPindelRecordParser.t | 8 +- setup.sh | 101 +--- 15 files changed, 1340 insertions(+), 296 deletions(-) create mode 100644 perl/bin/pindelCohort.pl create mode 100644 perl/bin/pindelCohort_to_vcf.pl create mode 100644 perl/lib/Sanger/CGP/Pindel/OutputGen/VcfCohortConverter.pm diff --git a/Dockerfile b/Dockerfile index 950a6bc..5afaf87 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,12 @@ -FROM quay.io/wtsicgp/dockstore-cgpmap:3.1.4 as builder +FROM quay.io/wtsicgp/pcap-core:5.0.4 as builder USER root # ALL tool versions used by opt-build.sh -ENV VER_CGPVCF="v2.2.1" -ENV VER_VCFTOOLS="0.1.16" +# need to keep in sync with setup.sh +ENV VER_CGPVCF="v2.2.1"\ + VER_VCFTOOLS="0.1.16"\ + VER_BLAT="v385" RUN apt-get -yq update RUN apt-get install -yq --no-install-recommends \ @@ -29,8 +31,11 @@ ENV LANG en_US.UTF-8 ADD build/opt-build.sh build/ RUN bash build/opt-build.sh $OPT +ADD build/opt-build-local.sh build/ +COPY c++ c++ +COPY perl perl + # build the tools in this repo, separate to reduce build time on errors -COPY . . RUN bash build/opt-build-local.sh $OPT FROM ubuntu:16.04 @@ -54,6 +59,7 @@ zlib1g \ liblzma5 \ libncurses5 \ p11-kit \ +moreutils \ unattended-upgrades && \ unattended-upgrade -d -v && \ apt-get remove -yq unattended-upgrades && \ diff --git a/build/opt-build-local.sh b/build/opt-build-local.sh index 9cfb57b..a8d5ade 100755 --- a/build/opt-build-local.sh +++ b/build/opt-build-local.sh @@ -63,3 +63,5 @@ if [ ! -e $SETUP_DIR/cgpPindel.success ]; then cd $SETUP_DIR touch $SETUP_DIR/cgpPindel.success fi + +rm -rf $SETUP_DIR diff --git a/build/opt-build.sh b/build/opt-build.sh index 21b0ff4..9ba6815 100644 --- a/build/opt-build.sh +++ b/build/opt-build.sh @@ -75,3 +75,6 @@ if [ ! -e $SETUP_DIR/cgpVcf.success ]; then rm -rf distro.* distro/* touch $SETUP_DIR/cgpVcf.success fi + +curl -sSL http://hgdownload.cse.ucsc.edu/admin/exe/linux.x86_64.${VER_BLAT}/blat/blat > $INST_PATH/bin/blat +chmod ugo+x $INST_PATH/bin/blat diff --git a/perl/Makefile.PL b/perl/Makefile.PL index ae59474..fbdd389 100755 --- a/perl/Makefile.PL +++ b/perl/Makefile.PL @@ -34,7 +34,10 @@ WriteMakefile( bin/FlagVcf.pl bin/pindel_merge_vcf_bam.pl bin/pindel_np_from_vcf.pl - bin/pindel_germ_bed.pl)], + bin/pindel_germ_bed.pl + bin/pindelCohort.pl + bin/pindelCohort_to_vcf.pl + )], PREREQ_PM => { 'Const::Fast' => 0.014, 'Try::Tiny' => 0.19, diff --git a/perl/bin/pindel.pl b/perl/bin/pindel.pl index 1fdabd9..61f7290 100755 --- a/perl/bin/pindel.pl +++ b/perl/bin/pindel.pl @@ -33,9 +33,7 @@ BEGIN use autodie qw(:all); use File::Path qw(remove_tree make_path); -use Getopt::Long; use File::Spec; -use Pod::Usage qw(pod2usage); use List::Util qw(first); use Const::Fast qw(const); use File::Copy; @@ -94,125 +92,42 @@ sub cleanup { } sub setup { - my %opts; - pod2usage(-msg => "\nERROR: Option must be defined.\n", -verbose => 1, -output => \*STDERR) if(scalar @ARGV == 0); - $opts{'cmd'} = join " ", $0, @ARGV; - GetOptions( 'h|help' => \$opts{'h'}, - 'm|man' => \$opts{'m'}, - 'c|cpus=i' => \$opts{'threads'}, - 'r|reference=s' => \$opts{'reference'}, - 'o|outdir=s' => \$opts{'outdir'}, - 't|tumour=s' => \$opts{'tumour'}, - 'n|normal=s' => \$opts{'normal'}, - 'e|exclude=s' => \$opts{'exclude'}, - 'b|badloci=s' => \$opts{'badloci'}, - 'p|process=s' => \$opts{'process'}, - 'i|index=i' => \$opts{'index'}, - 'v|version' => \$opts{'version'}, - # these are specifically for pin2vcf - 'sp|species=s{0,}' => \@{$opts{'species'}}, - 'as|assembly=s' => \$opts{'assembly'}, - 'st|seqtype=s' => \$opts{'seqtype'}, - 'sg|skipgerm' => \$opts{'skipgerm'}, - # specifically for FlagVCF - 's|simrep=s' => \$opts{'simrep'}, - 'f|filters=s' => \$opts{'filters'}, - 'g|genes=s' => \$opts{'genes'}, - 'u|unmatched=s' => \$opts{'unmatched'}, - 'sf|softfil=s' => \$opts{'softfil'}, - 'l|limit=i' => \$opts{'limit'}, - 'd|debug' => \$opts{'debug'}, - 'a|apid:s' => \$opts{'apid'}, - ) or pod2usage(2); - - pod2usage(-verbose => 1) if(defined $opts{'h'}); - pod2usage(-verbose => 2) if(defined $opts{'m'}); - - if($opts{'version'}) { - print 'Version: ',Sanger::CGP::Pindel::Implement->VERSION,"\n"; - exit 0; - } - - PCAP::Cli::file_for_reading('reference', $opts{'reference'}); - PCAP::Cli::file_for_reading('tumour', $opts{'tumour'}); - PCAP::Cli::file_for_reading('normal', $opts{'normal'}); - PCAP::Cli::file_for_reading('simrep', $opts{'simrep'}); - PCAP::Cli::file_for_reading('filters', $opts{'filters'}); - PCAP::Cli::file_for_reading('genes', $opts{'genes'}); - PCAP::Cli::file_for_reading('unmatched', $opts{'unmatched'}); - PCAP::Cli::file_for_reading('softfil', $opts{'softfil'}) if(defined $opts{'softfil'}); - PCAP::Cli::out_dir_check('outdir', $opts{'outdir'}); - my $final_logs = File::Spec->catdir($opts{'outdir'}, 'logs'); - if(-e $final_logs) { - warn "NOTE: Presence of '$final_logs' directory suggests successful complete analysis, please delete to rerun\n"; - exit 0; - } - - - delete $opts{'process'} unless(defined $opts{'process'}); - delete $opts{'index'} unless(defined $opts{'index'}); - delete $opts{'limit'} unless(defined $opts{'limit'}); - - delete $opts{'exclude'} unless(defined $opts{'exclude'}); - delete $opts{'badloci'} unless(defined $opts{'badloci'}); - delete $opts{'apid'} unless(defined $opts{'apid'}); - - if(exists $opts{'process'}) { - PCAP::Cli::valid_process('process', $opts{'process'}, \@VALID_PROCESS); - if(exists $opts{'index'}) { - my @valid_seqs = Sanger::CGP::Pindel::Implement::valid_seqs(\%opts); + my $opts = Sanger::CGP::Pindel::Implement::shared_setup( + ['tumour', 'normal'], + {'t|tumour=s' => 'tumour', 'n|normal=s' => 'normal'} + ); + PCAP::Cli::file_for_reading('tumour', $opts->{'tumour'}); + PCAP::Cli::file_for_reading('normal', $opts->{'normal'}); + + if(exists $opts->{'process'}) { + PCAP::Cli::valid_process('process', $opts->{'process'}, \@VALID_PROCESS); + if(exists $opts->{'index'}) { + my @valid_seqs = Sanger::CGP::Pindel::Implement::valid_seqs($opts); my $refs = scalar @valid_seqs; - my $max = $index_max{$opts{'process'}}; + my $max = $index_max{$opts->{'process'}}; if($max==-1){ - if(exists $opts{'limit'}) { - $max = $opts{'limit'} > $refs ? $refs : $opts{'limit'}; + if(exists $opts->{'limit'}) { + $max = $opts->{'limit'} > $refs ? $refs : $opts->{'limit'}; } else { $max = $refs; } } - die "ERROR: based on reference and exclude option index must be between 1 and $refs\n" if($opts{'index'} < 1 || $opts{'index'} > $max); - PCAP::Cli::opt_requires_opts('index', \%opts, ['process']); + die "ERROR: based on reference and exclude option index must be between 1 and $refs\n" if($opts->{'index'} < 1 || $opts->{'index'} > $max); + PCAP::Cli::opt_requires_opts('index', $opts, ['process']); die "No max has been defined for this process type\n" if($max == 0); - PCAP::Cli::valid_index_by_factor('index', $opts{'index'}, $max, 1); + PCAP::Cli::valid_index_by_factor('index', $opts->{'index'}, $max, 1); } } - elsif(exists $opts{'index'}) { + elsif(exists $opts->{'index'}) { die "ERROR: -index cannot be defined without -process\n"; } - # now safe to apply defaults - $opts{'threads'} = 1 unless(defined $opts{'threads'}); - $opts{'seqtype'} = 'WGS' unless(defined $opts{'seqtype'}); - - - # make all things that appear to be paths complete (absolute not great if BAM/BAI in different locations) - for my $key (keys %opts) { - next unless( first {$key eq $_} qw(reference outdir tumour normal badloci simrep filters genes unmatched softfil)); - $opts{$key} = cwd().'/'.$opts{$key} if(defined $opts{$key} && -e $opts{$key} && $opts{$key} !~ m/^\//); - } - - my $tmpdir = File::Spec->catdir($opts{'outdir'}, 'tmpPindel'); - make_path($tmpdir) unless(-d $tmpdir); - my $progress = File::Spec->catdir($tmpdir, 'progress'); - make_path($progress) unless(-d $progress); - my $logs = File::Spec->catdir($tmpdir, 'logs'); - make_path($logs) unless(-d $logs); - - $opts{'tmp'} = $tmpdir; - - if(scalar @{$opts{'species'}} > 0 ){ - $opts{'species'}="@{$opts{'species'}}"; - } - else { - delete $opts{'species'}; - } - - return \%opts; + return $opts; } __END__ diff --git a/perl/bin/pindelCohort.pl b/perl/bin/pindelCohort.pl new file mode 100644 index 0000000..f8eea80 --- /dev/null +++ b/perl/bin/pindelCohort.pl @@ -0,0 +1,192 @@ +#!/usr/bin/perl + +########## LICENCE ########## +# Copyright (c) 2020 Genome Research Ltd. +# +# Author: CASM/Cancer IT +# +# This file is part of cgpPindel. +# +# cgpPindel is free software: you can redistribute it and/or modify it under +# the terms of the GNU Affero 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 Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +########## LICENCE ########## + +BEGIN { + use Cwd qw(abs_path cwd); + use File::Basename; + unshift (@INC,dirname(abs_path($0)).'/../lib'); +}; + +use strict; +use warnings FATAL => 'all'; +use autodie qw(:all); +use Const::Fast qw(const); + +use PCAP::Cli; +use Sanger::CGP::Pindel::Implement; + +use Data::Dumper; + +const my @VALID_PROCESS => qw(input pindel ???); +my %index_max = ( 'input' => -1, + 'pindel' => -1, + '???' => 1, + ); + +{ + my $options = setup(); + my $threads = PCAP::Threaded->new($options->{'threads'}); + &PCAP::Threaded::disable_out_err if(exists $options->{'index'}); + + # register any process that can run in parallel here + $threads->add_function('input', \&Sanger::CGP::Pindel::Implement::input_cohort); + $threads->add_function('pindel', \&Sanger::CGP::Pindel::Implement::pindel); + + # start processes here (in correct order obviously), add conditions for skipping based on 'process' option + if(!exists $options->{'process'} || $options->{'process'} eq 'input') { + my $jobs = scalar @{$options->{'hts_files'}}; + $jobs = $options->{'limit'} if(exists $options->{'limit'} && defined $options->{'limit'}); + $threads->run($jobs, 'input', $options); + } + if(!exists $options->{'process'} || $options->{'process'} eq 'pindel') { + my $jobs = Sanger::CGP::Pindel::Implement::determine_jobs($options); # method still needed to populate info + $jobs = $options->{'limit'} if(exists $options->{'limit'} && defined $options->{'limit'}); + $threads->run($jobs, 'pindel', $options); + } +} + +sub index_check { + my $opts = shift; + my $max_files = @{$opts->{'hts_files'}}; + if(exists $opts->{'process'}) { + PCAP::Cli::valid_process('process', $opts->{'process'}, \@VALID_PROCESS); + if(exists $opts->{'index'}) { + my @valid_seqs = Sanger::CGP::Pindel::Implement::valid_seqs($opts); + my $refs = scalar @valid_seqs; + + my $max = $index_max{$opts->{'process'}}; + if($max==-1){ + if(exists $opts->{'limit'}) { + $max = $opts->{'limit'} > $refs ? $refs : $opts->{'limit'}; + } else { + if($opts->{'process'} eq 'input') { + $max = $max_files; + } else { + $max = $refs; + } + } + } + if($opts->{'index'} < 1 || $opts->{'index'} > $max) { + if($opts->{'process'} eq 'input') { + die "ERROR: based on number of inputs option -index must be between 1 and $max_files\n"; + } else { + die "ERROR: based on reference and exclude option -index must be between 1 and $refs\n"; + } + } + PCAP::Cli::opt_requires_opts('index', $opts, ['process']); + die "No max has been defined for this process type\n" if($max == 0); + } + } + elsif(exists $opts->{'index'}) { + die "ERROR: -index cannot be defined without -process\n"; + } +} + +sub setup { + my $opts = Sanger::CGP::Pindel::Implement::shared_setup([],{}); + + # add hts_files from the remains of @ARGV + Sanger::CGP::Pindel::Implement::cohort_files($opts); + index_check($opts); + + return $opts; +} + +__END__ + +=head1 pindelCohort.pl + +Similar to pindel.pl but processes 1 or more samples. References to BAM can ber replaced with CRAM. + +=head1 SYNOPSIS + +pindelCohort.pl [options] sample1.bam [sample2.bam sample3.bam...] + + Required parameters: + -outdir -o Folder to output result to. + -reference -r Path to reference genome file *.fa[.gz] + -simrep -s Full path to tabix indexed simple/satellite repeats. + -filter -f VCF filter rules file (see FlagVcf.pl for details) + -genes -g Full path to tabix indexed coding gene footprints. + -unmatched -u Full path to tabix indexed gff3 of unmatched normal panel + - see pindel_np_from_vcf.pl + + Optional + -seqtype -st Sequencing protocol, expect all input to match [WGS] + -assembly -as Name of assembly in use + - when not available in BAM header SQ line. + -species -sp Species + - when not available in BAM header SQ line. + -exclude -e Exclude this list of ref sequences from processing, wildcard '%' + - comma separated, e.g. NC_007605,hs37d5,GL% + -badloci -b Tabix indexed BED file of locations to not accept as anchors + - e.g. hi-seq depth from UCSC + -skipgerm -sg Don't output events with more evidence in normal BAM. + -cpus -c Number of cores to use. [1] + - recommend max 4 during 'input' process. + -softfil -sf VCF filter rules to be indicated in INFO field as soft flags + -limit -l When defined with '-cpus' internally thread concurrent processes. + - requires '-p', specifically for pindel/pin2vcf steps + -debug -d Don't cleanup workarea on completion. + -apid -a Analysis process ID (numeric) - for cgpAnalysisProc header info + - not necessary for external use + + Targeted processing (further detail under OPTIONS): + -process -p Only process this step then exit, optionally set -index + -index -i Optionally restrict '-p' to single job + + Other: + -help -h Brief help message. + -man -m Full documentation. + -version -v Version + + File list can be full file names or wildcard, e.g. + + pindelCohort.pl -c 4 -r some/genome.fa[.gz] -o myout sample1.bam sample2.bam + or + pindelCohort.pl -c 4 -r some/genome.fa[.gz] -o myout sample*.bam + or + pindelCohort.pl -c 4 -r some/genome.fa[.gz] -o myout sample*.cram + + Please note that colocated index and *.bas files are required. + +=head1 OPTIONS + +=over 2 + +=item B<-process> + +Available processes for this tool are: + + ??? + +=item B<-index> + +Possible index ranges for processes above are: + + ? + +If you want STDOUT/ERR to screen ensure index is set even for single job steps. + +=back + diff --git a/perl/bin/pindelCohort_to_vcf.pl b/perl/bin/pindelCohort_to_vcf.pl new file mode 100644 index 0000000..0e0048d --- /dev/null +++ b/perl/bin/pindelCohort_to_vcf.pl @@ -0,0 +1,317 @@ +#!/usr/bin/perl + +########## LICENCE ########## +# Copyright (c) 2020 Genome Research Ltd. +# +# Author: CASM/Cancer IT +# +# This file is part of cgpPindel. +# +# cgpPindel is free software: you can redistribute it and/or modify it under +# the terms of the GNU Affero 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 Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +########## LICENCE ########## + +use strict; +use warnings FATAL => 'all'; +use autodie qw(:all); +use Cwd qw(abs_path); +use Pod::Usage qw(pod2usage); +use FindBin qw($Bin); +use lib "$Bin/../lib"; + +use File::Basename; +use Getopt::Long; + +use Sanger::CGP::Pindel::Implement; +use Sanger::CGP::Pindel::OutputGen::VcfCohortConverter; +use Sanger::CGP::Vcf::Contig; +use Sanger::CGP::Vcf::Sample; +use Sanger::CGP::Pindel::OutputGen::PindelRecordParser; +use PCAP::Cli; +use PCAP::Bam::Bas; + +use Bio::DB::HTS; + +use Data::Dumper; + +{ + my $options = setup(); + my $contigs = contig_setup($options); + my ($hts_by_sample, $bas_by_sample, $vcfsamp_by_sample) = hts_and_sample_parse($options); + + my @samples = sort keys %{$hts_by_sample}; + my $record_converter = Sanger::CGP::Pindel::OutputGen::VcfCohortConverter->new( + -contigs => [values %$contigs], + -samples => \@samples, + -hts_set => $hts_by_sample, + -bas_set => $bas_by_sample, + -all => $options->{'all'}, + ); + my $input_source = basename($0). '_v'. Sanger::CGP::Pindel->VERSION; + my $header = $record_converter->gen_header($options->{'reference'}, $input_source, $vcfsamp_by_sample, $options); + my $out_fh = $options->{'output'}; + print $out_fh $header; + for my $in_file(@{$options->{'input'}}) { + process_pindel_file($options, $in_file, $out_fh, $record_converter); + } +} + +sub process_pindel_file { + my ($options, $pindel_file, $out_fh, $record_converter) = @_; + my $prp = Sanger::CGP::Pindel::OutputGen::PindelRecordParser->new( + -path => $pindel_file, + -fai => Bio::DB::HTS::Fai->load($options->{'reference'}), + -noreads => 1, + ); +my $records = 0; + while(my $record = $prp->next_record) { +#next unless($record->idx eq 'D36'); +last if($records > 40); + print $out_fh $record_converter->gen_record($record); +$records++; +#die "told to exit for testing"; + } +} + +sub hts_and_sample_parse { + my $options = shift; + my %hts_by_sample; + my %bas_by_sample; + my %vcf_samples; + for my $hts_f(@{$options->{'hts_files'}}) { + my $bas_f = $hts_f.'.bas'; + die "ERROR: Failed to find colocated bas file at: $bas_f" unless(-e $bas_f); + my $hts = Bio::DB::HTS->new(-bam => $hts_f, -fasta => $options->{reference}); + my ($sample, $platform); + foreach my $line (split(/\n/,$hts->header->text)) { + next unless($line =~ m/^\@RG/); + chomp $line; + ($sample) = $line =~ m/SM:([^\t]+)/; + ($platform) = $line =~ /PL:([^\t]+)/; + last if(defined $sample); + } + $hts_by_sample{$sample} = $hts_f; + $bas_by_sample{$sample} = PCAP::Bam::Bas->new($bas_f); + die sprintf "Failed to find sample name in \@RG header lines of %s", $hts->hts_path unless(defined $sample); + $vcf_samples{$sample} = Sanger::CGP::Vcf::Sample->new( + -name => $sample, + -study => $options->{'project'}, + -platform => $platform, + -seq_protocol => $options->{'protocol'}, + -description => $sample + ); + + } + return (\%hts_by_sample, \%bas_by_sample, \%vcf_samples); +} + +sub _contig_parse { + my ($hts, $hts_contigs, $fixed_contigs) = @_; + my ($assembly_out, $species_out); + foreach my $line (split(/\n/,$hts->header->text)){ + next unless($line =~ /^\@SQ/); + my ($name) = $line =~ /SN:([^\t]+)/; + my ($length) = $line =~ /LN:([^\t]+)/; + my ($assembly) = $line =~ /AS:([^\t]+)/; + my ($species) = $line =~ /SP:([^\t]+)/; + if(defined $assembly && !defined $assembly_out) { + $assembly_out = $assembly; + } + if(defined $species && !defined $species_out) { + $species_out = $species; + } + if($fixed_contigs) { + if(!exists $hts_contigs->{$name}) { + die sprintf "ERROR: Found contig %s in file %s but this is not consistent with other files.", $name, $hts->hts_path; + } + } + else { + $hts_contigs->{$name} = $length; + } + } + return ($assembly_out, $species_out); +} + +sub contig_setup { + my $options = shift; + my @hts; # to store objects + my $hts_contigs = {}; # to store contig list + my ($assembly, $species); + + $assembly = $options->{'assembly'} if(defined $options->{'assembly'}); + $species = join q{ }, @{$options->{'species'}} if(defined $options->{'species'} && @{$options->{'species'}} > 0); + + my @paths; + my $not_first = 0; + for my $hts_f(@{$options->{'hts_files'}}) { + my $hts = Bio::DB::HTS->new(-bam => $hts_f, -fasta => $options->{reference}); + my ($assembly_found, $species_found) = _contig_parse($hts, $hts_contigs, $not_first++); + if(! defined $assembly && defined $assembly_found) { + $assembly = $assembly_found; + } + if(defined $assembly && defined $assembly_found && $assembly ne $assembly_found) { + if(defined $options->{'assembly'}){ + die sprintf "Assembly defined on command line (%s) doesn't match that found in %s (%s)", $options->{'assembly'}, $hts->hts_path, $assembly_found; + } + else { + die sprintf "Assembly defined in %s (%s) doesn't match that found in previous files (%s):%s\n", $hts->hts_path, $assembly_found, $assembly, join(qq{\n\t}, @paths); + } + } + if(! defined $species && defined $species_found) { + $species = $species_found; + } + if(defined $species && defined $species_found && $species ne $species_found) { + if(defined $options->{'species'}){ + die sprintf "Species defined on command line (%s) doesn't match that found in %s (%s)", $options->{'species'}, $hts->hts_path, $species_found; + } + else { + die sprintf "Species defined in %s (%s) doesn't match that found in previous files (%s):%s\n", $hts->hts_path, $species_found, $species, join(qq{\n\t}, @paths); + } + } + push @paths, $hts->hts_path; + } + die "No assembly defined in BAM/CRAM headers please specify in command options." unless(defined $assembly); + die "No species defined in BAM/CRAM headers please specify in command options." unless(defined $species); + for my $name(keys %{$hts_contigs}) { + my $contig = Sanger::CGP::Vcf::Contig->new( + -name => $name, + -length => $hts_contigs->{$name}, + -assembly => $assembly, + -species => $species + ); + $hts_contigs->{$name} = $contig; + } + return $hts_contigs; +} + +sub setup{ + my %opts = ( + 'cmd' => join(" ", $0, @ARGV), + 'all' => 0, + ); + GetOptions( 'h|help' => \$opts{'h'}, + 'm|man' => \$opts{'m'}, + 'v|version' => \$opts{'v'}, + 'o|output:s' => \$opts{'output'}, + 'r|ref=s' => \$opts{'reference'}, + 'i|input=s@' => \$opts{'input'}, + 'prj|project:s' => \$opts{'project'}, + 'p|prot:s' => \$opts{'protocol'}, + 'as|assembly:s' => \$opts{'assembly'}, + 'sp|species=s{0,}' => \@{$opts{'species'}}, + 'pp|parent:s' => \$opts{'pp'}, + 'a|all' => \$opts{'all'} + ); + + if(defined $opts{'v'}){ + printf "Version: %s\n", Sanger::CGP::Pindel::Implement->VERSION; + exit; + } + + pod2usage(-verbose => 1) if(defined $opts{'h'}); + pod2usage(-verbose => 2) if(defined $opts{'m'}); + + PCAP::Cli::file_for_reading('ref', $opts{'reference'}); + my $i=1; + for my $if(@{$opts{'input'}}) { + PCAP::Cli::file_for_reading(sprintf('input(%d)', $i++), $if); + } + + $opts{'output'} = \*STDOUT unless($opts{'output'}); + + # add hts_files from the remains of @ARGV + Sanger::CGP::Pindel::Implement::cohort_files(\%opts); + + return \%opts; +} + + +__END__ + +=head1 NAME + +pindelCohort_to_vcf.pl - Takes a raw Pindel file and a set of bam files and produces a vcf file. + +=head1 SYNOPSIS + +pindelCohort_to_vcf.pl [options] SAMPLE1.bam [SAMPLE2.bam ...] + + Required parameters: + -ref -r File path to the reference file used to provide the coordinate system. + -input -i Files to read in, repeatable + + Optional parameters: + -output -o File path to output to. Defaults to STDOUT. + -all -a Generate VAF for all samples, even when not seen by Pindel. + + -project -prj String representing the project data is from. + -prot -p String representing the sequencing protocol (e.g. genomic, targeted, RNA-seq). + -assembly -as Reference assembly name, used when not found in BAM/CRAM headers. + -species -sp Species name, used when not found in BAM/CRAM headers. + -parent -pp Process information from parent program (where one exists) + + Other: + -help -h Brief help message. + -man -m Full documentation. + -version -v Prints the version number. + +=head1 OPTIONS + +=over 8 + +=item B<-input> + +File path(s) to read. Accepts only raw pindel files, repeat for multiple. + +=item B<-output> + +File path to output data. If this option is omitted the script will attempt to write to STDOUT. + + +=item B<-project> + +String identifying the project to which the samples belong. + +=item B<-prot> + +String representing the sequencing protocol (e.g. genomic, targeted, RNA-seq). + +=item B<-assembly> + +Reference assembly name, used when not found in BAM headers. Validated against header if both are present. + +=item B<-species> + +Species name, used when not found in BAM headers. Validated against header if both are present. + +=item B<-help> + +Print a brief help message and exits. + +=item B<-man> + +Prints the manual page and exits. + +=item B<-version> + +Prints the version number and exits. + +=back + +=head1 DESCRIPTION + +B will attempt to generate a vcf file from a Pindel output file. + +For every variant called by Pindel a blat will be performed and the results merged into a single vcf record. + +=cut diff --git a/perl/lib/Sanger/CGP/Pindel/Implement.pm b/perl/lib/Sanger/CGP/Pindel/Implement.pm index 23a353b..285054a 100644 --- a/perl/lib/Sanger/CGP/Pindel/Implement.pm +++ b/perl/lib/Sanger/CGP/Pindel/Implement.pm @@ -25,6 +25,7 @@ package Sanger::CGP::Pindel::Implement; use strict; use warnings FATAL => 'all'; use autodie qw(:all); +use Cwd qw(cwd); use Const::Fast qw(const); use File::Spec; use File::Which qw(which); @@ -34,6 +35,8 @@ use File::Temp qw(tempfile); use Capture::Tiny; use List::Util qw(first); use FindBin qw($Bin); +use Getopt::Long; +use Pod::Usage qw(pod2usage); use Sanger::CGP::Pindel; @@ -51,6 +54,46 @@ const my $FLAG => q{ -a %s -u %s -s %s -i %s -o %s -r %s}; const my $PIN_GERM => q{ -f %s -i %s -o %s}; const my $BASE_GERM_RULE => 'F012'; # prefixed with additional F if fragment filtering. +sub input_cohort{ + my ($index, $options) = @_; + return 1 if(exists $options->{'index'} && $index != $options->{'index'}); + + my $tmp = $options->{'tmp'}; + return 1 if PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), $index); + + my @inputs = @{$options->{'hts_files'}}; + my $iter = 1; + for my $input(@inputs) { + next if($iter++ != $index); # skip to the relevant input in the list + + ## build command for this index + # + + my $max_threads = $options->{'threads'}; + unless (exists $options->{'index'}) { + $max_threads = int ($max_threads / scalar @inputs); + } + $max_threads = 1 if($max_threads == 0); + + my $sample = sanitised_sample_from_bam($input); + my $gen_out = File::Spec->catdir($tmp, $sample); + make_path($gen_out) unless(-e $gen_out); + + my $command = "$^X "; + $command .= _which('pindel_input_gen.pl'); + $command .= sprintf $PINDEL_GEN_COMM, $input, $gen_out, $max_threads; + $command .= " -r $options->{reference}"; + $command .= " -e $options->{badloci}" if(exists $options->{'badloci'}); + + PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), $command, $index); + + # + ## The rest is auto-magical + PCAP::Threaded::touch_success(File::Spec->catdir($tmp, 'progress'), $index); + } + return 1; +} + sub input { my ($index, $options) = @_; return 1 if(exists $options->{'index'} && $index != $options->{'index'}); @@ -355,7 +398,17 @@ sub determine_jobs { my $tmp = $options->{'tmp'}; my @valid_seqs = valid_seqs($options); my %seqs; - for my $in_bam($options->{'tumour'}, $options->{'normal'}) { + my @samples; + if(exists $options->{'tumour'} && exists $options->{'normal'}) { + push @samples, $options->{'tumour'} && exists $options->{'normal'}; + } + elsif(exists $options->{'hts_files'}) { + @samples = @{$options->{'hts_files'}}; + } + else { + die "ERROR: Unexpected combination of BAM/CRAM inputs"; + } + for my $in_bam(@samples) { my $samp_path = File::Spec->catdir($tmp, sanitised_sample_from_bam($in_bam)); my @files = file_list($samp_path, qr/\.txt(:?\.gz)$/); for my $file(@files) { @@ -431,6 +484,131 @@ sub _which { return $path; } +sub shared_setup { + my ($are_paths, $extra_opts) = @_; + my %opts; + pod2usage(-msg => "\nERROR: Option must be defined.\n", -verbose => 1, -output => \*STDERR) if(scalar @ARGV == 0); + $opts{'cmd'} = join " ", $0, @ARGV; + my %load_opts = ( + 'h|help' => \$opts{'h'}, + 'm|man' => \$opts{'m'}, + 'c|cpus=i' => \$opts{'threads'}, + 'r|reference=s' => \$opts{'reference'}, + 'o|outdir=s' => \$opts{'outdir'}, + 'e|exclude=s' => \$opts{'exclude'}, + 'b|badloci=s' => \$opts{'badloci'}, + 'p|process=s' => \$opts{'process'}, + 'i|index=i' => \$opts{'index'}, + 'v|version' => \$opts{'version'}, + # these are specifically for pin2vcf + 'sp|species=s{0,}' => \@{$opts{'species'}}, + 'as|assembly=s' => \$opts{'assembly'}, + 'st|seqtype=s' => \$opts{'seqtype'}, + 'sg|skipgerm' => \$opts{'skipgerm'}, + # specifically for FlagVCF + 's|simrep=s' => \$opts{'simrep'}, + 'f|filters=s' => \$opts{'filters'}, + 'g|genes=s' => \$opts{'genes'}, + 'u|unmatched=s' => \$opts{'unmatched'}, + 'sf|softfil=s' => \$opts{'softfil'}, + 'l|limit=i' => \$opts{'limit'}, + 'd|debug' => \$opts{'debug'}, + 'a|apid:s' => \$opts{'apid'}, + ); + for my $opt_key(keys %{$extra_opts}) { + my $v = $extra_opts->{$opt_key}; + $load_opts{$opt_key} = \$opts{$v}; + } + GetOptions(%load_opts) or pod2usage(2); + + pod2usage(-verbose => 1) if(defined $opts{'h'}); + pod2usage(-verbose => 2) if(defined $opts{'m'}); + + if($opts{'version'}) { + print 'Version: ',Sanger::CGP::Pindel::Implement->VERSION,"\n"; + exit 0; + } + + PCAP::Cli::file_for_reading('reference', $opts{'reference'}); + PCAP::Cli::file_for_reading('simrep', $opts{'simrep'}); + PCAP::Cli::file_for_reading('filters', $opts{'filters'}); + PCAP::Cli::file_for_reading('genes', $opts{'genes'}); + PCAP::Cli::file_for_reading('unmatched', $opts{'unmatched'}); + PCAP::Cli::file_for_reading('softfil', $opts{'softfil'}) if(defined $opts{'softfil'}); + PCAP::Cli::out_dir_check('outdir', $opts{'outdir'}); + my $final_logs = File::Spec->catdir($opts{'outdir'}, 'logs'); + if(-e $final_logs) { + warn "NOTE: Presence of '$final_logs' directory suggests successful complete analysis, please delete to rerun\n"; + exit 0; + } + + delete $opts{'process'} unless(defined $opts{'process'}); + delete $opts{'index'} unless(defined $opts{'index'}); + delete $opts{'limit'} unless(defined $opts{'limit'}); + + delete $opts{'exclude'} unless(defined $opts{'exclude'}); + delete $opts{'badloci'} unless(defined $opts{'badloci'}); + delete $opts{'apid'} unless(defined $opts{'apid'}); + + # now safe to apply defaults + $opts{'threads'} = 1 unless(defined $opts{'threads'}); + $opts{'seqtype'} = 'WGS' unless(defined $opts{'seqtype'}); + + + # make all things that appear to be paths complete (absolute not great if BAM/BAI in different locations) + for my $key (keys %opts) { + next unless( first {$key eq $_} (qw(reference outdir badloci simrep filters genes unmatched softfil), @{$are_paths}) ); + $opts{$key} = cwd().'/'.$opts{$key} if(defined $opts{$key} && -e $opts{$key} && $opts{$key} !~ m/^\//); + } + + my $tmpdir = File::Spec->catdir($opts{'outdir'}, 'tmpPindel'); + make_path($tmpdir) unless(-d $tmpdir); + my $progress = File::Spec->catdir($tmpdir, 'progress'); + make_path($progress) unless(-d $progress); + my $logs = File::Spec->catdir($tmpdir, 'logs'); + make_path($logs) unless(-d $logs); + + $opts{'tmp'} = $tmpdir; + + if(scalar @{$opts{'species'}} > 0 ){ + $opts{'species'}="@{$opts{'species'}}"; + } + else { + delete $opts{'species'}; + } + + return \%opts; +} + +sub cohort_files { + my $opts = shift; + my @files; + my %uniq_chk; + for my $candidate(sort @ARGV) { + if(-e $candidate && -s _ && $candidate =~ m/[.](bam|cram)$/) { + $candidate = cwd().'/'.$candidate if($candidate !~ m{^/}); + if(exists $uniq_chk{$candidate}) { + die "Same file defined multiple times: $candidate"; + } + $uniq_chk{$candidate} = 1; + push @files, $candidate; + } + } + if(@files == 0) { + die "ERROR: Failed to find list of bam/cram files following arguments"; + } + for my $hts(@files) { + if(!-e "$hts.bai" && !-e "$hts.csi" && !-e "$hts.crai") { + die "ERROR: Failed to identify appropriate bam/cram index for $hts"; + } + unless(-e "$hts.bas") { + die "ERROR: Failed to identify appropriate *.bas file for $hts"; + } + } + $opts->{'hts_files'} = \@files; + return 1; +} + 1; __END__ diff --git a/perl/lib/Sanger/CGP/Pindel/InputGen/Pair.pm b/perl/lib/Sanger/CGP/Pindel/InputGen/Pair.pm index 355eb10..1641561 100644 --- a/perl/lib/Sanger/CGP/Pindel/InputGen/Pair.pm +++ b/perl/lib/Sanger/CGP/Pindel/InputGen/Pair.pm @@ -47,33 +47,34 @@ sub new { sub exact { my $self = shift; - return 1 if($self->{'r1'}->exact && $self->{'r2'}->exact); + # r2 least likely to be exact so test it first, short-circuit by not testing r1 if not exact + return 1 if($self->{'r2'}->exact && $self->{'r1'}->exact); return 0; } sub unmapped_pair { my $self = shift; + # r1 least likely to be unmapped so test it first, short-circuit by not testing r2 if unmapped return 1 if($self->{'r1'}->unmapped && $self->{'r2'}->unmapped); return 0; } sub qcfailed_pair { my $self = shift; + # r1 least likely to be qc_fail so test it first, short-circuit by not testing r2 if qc_fail return 1 if($self->{'r1'}->qc_failed && $self->{'r2'}->qc_failed); return 0; } sub has_good_anchor { my $self = shift; - my $r2_state = $self->{'r2'}->good_anchor; # to ensure both fully populates - return 1 if($self->{'r1'}->good_anchor || $r2_state); + return 1 if($self->{'r1'}->good_anchor || $self->{'r2'}->good_anchor); return 0; } sub has_good_query { my $self = shift; - my $r2_state = $self->{'r2'}->good_query; # to ensure both fully populates - return 1 if($self->{'r1'}->good_query || $r2_state); + return 1 if($self->{'r1'}->good_query || $self->{'r2'}->good_query); return 0; } diff --git a/perl/lib/Sanger/CGP/Pindel/InputGen/Read.pm b/perl/lib/Sanger/CGP/Pindel/InputGen/Read.pm index 41365cc..63b12fd 100644 --- a/perl/lib/Sanger/CGP/Pindel/InputGen/Read.pm +++ b/perl/lib/Sanger/CGP/Pindel/InputGen/Read.pm @@ -55,11 +55,9 @@ const my $MAX_CIGAR_OPS_FOR_ANCHOR => 7*2; #cigar operations array both elements sub new { my ($class, $sam, $end, $tabix) = @_; -# my @elements = (split /\t/, ${$sam})[0,1,2,3,4,5,8,9,10]; my ($qname, $flag, $rname, $pos, $mapq, $cigar, $seq, $qual) = (split /\t/, ${$sam})[0,1,2,3,4,5,9,10]; # just clean this up as it is of no use $cigar =~ s/[[:digit:]]+H//g if(index($cigar, 'H') != -1); - my ($rg) = ${$sam} =~ m/\tRG:Z:([^\t]+)/; my $self = {'qname' => $qname, 'flag' => int $flag, 'rname' => $rname, @@ -69,7 +67,7 @@ sub new { 'seq' => $seq, 'qual' => $qual, 'end' => int $end, - 'rg' => defined $rg ? $rg : '.', + 'rg' => ${$sam} =~ m/\tRG:Z:([^\t]+)/ ? $1 : q{.}, }; $self->{'tabix'} = $tabix if(defined $tabix); bless $self, $class; diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/PindelRecord.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/PindelRecord.pm index 811baf7..4d85fe1 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/PindelRecord.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/PindelRecord.pm @@ -1,23 +1,23 @@ package Sanger::CGP::Pindel::OutputGen::PindelRecord; ########## LICENCE ########## -# Copyright (c) 2014-2018 Genome Research Ltd. -# +# Copyright (c) 2014-2018 Genome Research Ltd. +# # Author: CASM/Cancer IT -# +# # This file is part of cgpPindel. -# -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 Affero General Public License for more -# details. -# -# You should have received a copy of the GNU Affero General Public License +# +# cgpPindel is free software: you can redistribute it and/or modify it under +# the terms of the GNU Affero 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 Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . ########## LICENCE ########## @@ -55,6 +55,8 @@ sub new{ _repeats => $args{'-repeats'}, _num_samples => $args{'-num_samples'}, _sample_contrib => $args{'-sample_contrib'}, + _ref_left => $args{'-ref_left'}, + _ref_right => $args{'-ref_right'}, }; bless $self, $class; return $self; @@ -127,6 +129,14 @@ sub get_reads{ return $self->{_reads}->{$sample_name}->{$strand}; } +sub get_read_counts{ + my($self,$sample_name,$strand) = @_; + if(exists $self->{_reads}->{$sample_name}->{$strand}) { + return scalar @{$self->{_reads}->{$sample_name}->{$strand}}; + } + return 0; +} + =head samples Returns an array of UNORDERED sample names asscociated with the record. @@ -178,6 +188,18 @@ sub min_change{ return $self->{_min_change}; } +sub ref_left { + my($self,$value) = @_; + $self->{_ref_left} = $value if defined $value; + return $self->{_ref_left}; +} + +sub ref_right { + my($self,$value) = @_; + $self->{_ref_right} = $value if defined $value; + return $self->{_ref_right}; +} + sub lub{ my($self,$value) = @_; $self->{_lub} = $value if defined $value; diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/PindelRecordParser.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/PindelRecordParser.pm index dae9ce2..39e62b8 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/PindelRecordParser.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/PindelRecordParser.pm @@ -57,6 +57,7 @@ sub init{ $self->{_fh} = $fh; $self->{_fai} = $args{-fai}; + $self->{_noreads} = $args{-noreads} || 0; ## clear the first line of ##+.... @@ -300,6 +301,9 @@ sub _parse_alignment { my ($ref_left, $ref_change, $ref_right) = ($$ref_line =~ m/([A-Z]+)(\s+|[a-z]+|[a-z]+.*[a-z]+)([A-Z]+)/); + $record->ref_left($ref_left); + $record->ref_right($ref_right); + my $change_ref_offset = length $ref_left; my $change_ref_offset_end = $change_ref_offset + length $ref_change; my $record_type = $record->type(); @@ -353,7 +357,7 @@ sub _parse_alignment { foreach my $read(@{$alignment}) { $read =~ s/ ([+-])/\t$1/ if($record_type eq 'D'); ## correction for a bug in the pindel output layout.... this is done here to allow use to pass a ref of the read into _parse_read - _parse_read($record, $chr, $start_pos, \$read, $ref_seq_length, ($read_num++.$record_idx), $change_ref_offset, $change_ref_offset_end,\$_buffer_region,$_buffer_region_start); + _parse_read($record, $chr, $start_pos, \$read, $ref_seq_length, ($read_num++.$record_idx), $change_ref_offset, $change_ref_offset_end,\$_buffer_region,$_buffer_region_start, $self->{_noreads}); } ## This is not strictly read from the pindel input but is useful for woring out the number of repeats within the repeat-range. @@ -511,82 +515,80 @@ between bwa and pindel. is used to grab variant sequence from the read string. =cut sub _parse_read { - my ($record, $chr, $start_pos, $read, $ref_seq_length, $read_idx, $change_ref_start, $change_ref_end, $_buffer_region, $_buffer_region_start) = @_; + my ($record, $chr, $start_pos, $read, $ref_seq_length, $read_idx, $change_ref_start, $change_ref_end, $_buffer_region, $_buffer_region_start, $no_read_data) = @_; my @bits = split /\t+/, ${$read}; - - ## This is a custom read name component added to the read name when it is put into Pindel. - ## As pindel currently does not preserve the read group, if we want to identify read group - ## specific errors we need to track the read groups from the reads.... - my ($read_group) = $bits[-1] =~ /\/[12]_RG(.+)$/; - $read_group = '' unless $read_group; - - my ($name, $rg_pair) = split /\//, $bits[-1]; - $name = substr($name,1) if substr($name,0,1) eq '@'; - - # need this to force uniqness in reads that have multiple events - # and make display in gbrowse work for overlapping reads - $name .= '_r'.$read_idx; - my $sample = $bits[-2]; - my $mapq = $bits[-3]; # mapq of anchor read, sensible to use this in the output - my $strand = $bits[-5]; # will need to be inverted as is the strand of the anchor - - # locate the left and right parts of the read string - my $read_seq = $bits[0]; - my $read_left = substr($read_seq, 0, $change_ref_start); - my $read_right = substr($read_seq, $change_ref_end); - my $event = substr($read_seq, $change_ref_start,$change_ref_end - $change_ref_start); - - ## we do this so that we can efficiently strip the space characters from the read components... - my $left_seq_length = $read_left =~ tr/ATCGN/ATCGN/;## the tr simply counts the number of atcgs in the string... v-efficient - my $right_seq_length = $read_right =~ tr/ATCGN/ATCGN/;## the tr simply counts the number of atcgs in the string... v-efficient - my $event_seq_length = $event =~ tr/ATCGN/ATCGN/; - my $event_length = length $event; - - ## create a read sequence without any spaces. This is MUCH MUCH faster than using s///. - my $space_stripped_read_seq = substr($read_left, (length($read_left) - $left_seq_length), $change_ref_start); - $space_stripped_read_seq .= $event if $event_seq_length; - $space_stripped_read_seq .= substr($read_right, 0,$right_seq_length); - - #$read_left =~ s/^ +//; - #$read_right =~ s/ //g; - #$read_seq =~ s/ //g; - - $start_pos -= $left_seq_length ; - - # Some data have -ve position starts so need to be corrected. i.e. the position starts before the beginning of the reference. - # This occurs with species like Devil that map to shattered contig sequences. - if($start_pos < 1) { - my $corr_size = (abs $start_pos)+1; - #$read_seq = substr($read_seq, $corr_size); - #$read_left = substr($read_left, $corr_size); - $read_seq = substr($space_stripped_read_seq, $corr_size); - $read_left = substr(substr($read_left, (length($read_left) - $left_seq_length),$change_ref_start), $corr_size); - - $left_seq_length = $read_left =~ tr/ATCGN/ATCGN/; - $start_pos = 1; - } + my $strand = $bits[-5]; + $strand =~ tr/\+\-/\-\+/; # need to invert as is strand of anchor -## Keep track of all the reads associated with a variant. -## These bits are for pindel_bam creation. -## These bam files only contain the reads identified from within pindel as having a variant -## These bam files are used in things like gbrowse/jbrowse for display + my $read_data; + if($no_read_data == 0) { + ## This is a custom read name component added to the read name when it is put into Pindel. + ## As pindel currently does not preserve the read group, if we want to identify read group + ## specific errors we need to track the read groups from the reads.... + my ($read_group) = $bits[-1] =~ /\/[12]_RG(.+)$/; + $read_group = '' unless $read_group; + + my ($name, $rg_pair) = split /\//, $bits[-1]; + $name = substr($name,1) if substr($name,0,1) eq '@'; + + # need this to force uniqness in reads that have multiple events + # and make display in gbrowse work for overlapping reads + $name .= '_r'.$read_idx; + + my $mapq = $bits[-3]; # mapq of anchor read, sensible to use this in the output + + # locate the left and right parts of the read string + my $read_seq = $bits[0]; + my $read_left = substr($read_seq, 0, $change_ref_start); + my $read_right = substr($read_seq, $change_ref_end); + my $event = substr($read_seq, $change_ref_start,$change_ref_end - $change_ref_start); + + ## we do this so that we can efficiently strip the space characters from the read components... + my $left_seq_length = $read_left =~ tr/ATCGN/ATCGN/;## the tr simply counts the number of atcgs in the string... v-efficient + my $right_seq_length = $read_right =~ tr/ATCGN/ATCGN/;## the tr simply counts the number of atcgs in the string... v-efficient + my $event_seq_length = $event =~ tr/ATCGN/ATCGN/; + my $event_length = length $event; + + ## create a read sequence without any spaces. This is MUCH MUCH faster than using s///. + my $space_stripped_read_seq = substr($read_left, (length($read_left) - $left_seq_length), $change_ref_start); + $space_stripped_read_seq .= $event if $event_seq_length; + $space_stripped_read_seq .= substr($read_right, 0,$right_seq_length); + + $start_pos -= $left_seq_length ; + + # Some data have -ve position starts so need to be corrected. i.e. the position starts before the beginning of the reference. + # This occurs with species like Devil that map to shattered contig sequences. + if($start_pos < 1) { + my $corr_size = (abs $start_pos)+1; + $read_seq = substr($space_stripped_read_seq, $corr_size); + $read_left = substr(substr($read_left, (length($read_left) - $left_seq_length),$change_ref_start), $corr_size); + + $left_seq_length = $read_left =~ tr/ATCGN/ATCGN/; + $start_pos = 1; + } - my @cig_list = ($left_seq_length, 'M'); - push @cig_list, $ref_seq_length, 'D' if $ref_seq_length; - push @cig_list, $event_seq_length, 'I' if $event_seq_length; - push @cig_list, $right_seq_length, 'M'; + ## Keep track of all the reads associated with a variant. + ## These bits are for pindel_bam creation. + ## These bam files only contain the reads identified from within pindel as having a variant + ## These bam files are used in things like gbrowse/jbrowse for display - $strand =~ tr/\+\-/\-\+/; # need to invert as is strand of anchor - my $flag = $strand eq '-' ? 16 : 0; # previously 1+8 but as not really paired anymore - my @tags = calmd($chr, $start_pos, \@cig_list, \$space_stripped_read_seq, $_buffer_region, $_buffer_region_start); + my @cig_list = ($left_seq_length, 'M'); + push @cig_list, $ref_seq_length, 'D' if $ref_seq_length; + push @cig_list, $event_seq_length, 'I' if $event_seq_length; + push @cig_list, $right_seq_length, 'M'; - pop @tags; # we dont need the last value.. at the moment... - unshift @tags, "RG:Z:$read_group" if($read_group); + my $flag = $strand eq '-' ? 16 : 0; # previously 1+8 but as not really paired anymore + my @tags = calmd($chr, $start_pos, \@cig_list, \$space_stripped_read_seq, $_buffer_region, $_buffer_region_start); + + pop @tags; # we dont need the last value.. at the moment... + unshift @tags, "RG:Z:$read_group" if($read_group); + $read_data = [$name,$flag,$chr,$start_pos,$mapq,join(q{},@cig_list),'*','0','0',$space_stripped_read_seq,'*',@tags]; + } # Create a basic sam line for the read and add it to the record. - $record->add_read($sample,$strand,[$name,$flag,$chr,$start_pos,$mapq,join(q{},@cig_list),'*','0','0',$space_stripped_read_seq,'*',@tags]); + $record->add_read($sample,$strand,$read_data); return 1; } diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfCohortConverter.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfCohortConverter.pm new file mode 100644 index 0000000..3e0fea0 --- /dev/null +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfCohortConverter.pm @@ -0,0 +1,486 @@ +package Sanger::CGP::Pindel::OutputGen::VcfCohortConverter; + +########## LICENCE ########## +# Copyright (c) 2014-2018 Genome Research Ltd. +# +# Author: CASM/Cancer IT +# +# This file is part of cgpPindel. +# +# cgpPindel is free software: you can redistribute it and/or modify it under +# the terms of the GNU Affero 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 Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +########## LICENCE ########## + +use strict; +use File::Basename; +use File::Temp qw(tempfile); +use Capture::Tiny qw(capture); + +use Data::Dumper; + +use Sanger::CGP::Pindel; +use Sanger::CGP::Vcf::VcfUtil; +use Sanger::CGP::Vcf::VcfProcessLog; +use Const::Fast qw(const); + +use Vcf; + +const my $SEP => "\t"; +const my $NL => "\n"; +const my $READS_AND_BLAT => q{bash -c 'set -o pipefail ; samtools view -uF 3840 %s %s:%d-%d | samtools fasta - > %s && blat -t=dna -q=dna -noTrimA -minIdentity=95 -noHead -out=pslx -out=pslx %s %s /dev/stdout'}; +# returns +ve and then -ve results +const my $SAM_DEPTH_PN => q{bash -c "set -o pipefail ; samtools view -uF 3844 %s %s:%d-%d | pee 'samtools view -c -F 16 -' 'samtools view -c -f 16 -'"}; + +const my $MATCH => 0; +const my $Q_GAP => 4; +const my $T_GAP => 6; +const my $T_BASES => 7; +const my $STRAND => 8; +const my $Q_NAME => 9; +const my $Q_SIZE => 10; +const my $Q_START => 11; +const my $T_NAME => 13; +const my $T_START => 15; +const my $T_END => 16; +const my $BLOCK_COUNT => 17; +const my $BLOCK_SIZES => 18; +const my $Q_STARTS => 19; +const my $T_STARTS => 20; +const my $Q_SEQ => 21; +const my $T_SEQ => 22; + +1; + +sub new{ + my $proto = shift; + my (%args) = @_; + my $class = ref($proto) || $proto; + + my $self = {}; + bless $self, $class; + + $self->init(%args); + + return $self; +} + +sub init{ + my($self,%args) = @_; + $self->{_contigs} = $args{-contigs}; + $self->{_srt_samples} = $args{-samples}; + $self->{_hts_set} = $args{-hts_set}; + $self->{_bas_set} = $args{-bas_set}; + $self->_max_inserts() if(defined $self->{_bas_set}); + $self->{_all} = $args{-all}; +} + +sub _max_inserts { + my $self = shift; + my %bas = %{$self->{_bas_set}}; + my %ins_by_sample; + for my $s(keys %bas) { + my $max_ins = 0; + for my $rg($bas{$s}->read_groups) { + my $m_sd2 = $bas{$s}->get($rg, 'mean_insert_size') + ($bas{$s}->get($rg, 'insert_size_sd') * 2); + $max_ins = $m_sd2 if($m_sd2 > $max_ins); + } + $ins_by_sample{$s} = $max_ins; + } + $self->{_ins_set} = \%ins_by_sample; +} + + +=head gen_header + +Generates a Vcf header String for NORMAL/TUMOUR comparisons. + +@param1 reference_path - a String containing the path to the reference used in the VCF. + +@param2 input_source - a String containing the name and version of the application or source of the VCF data. + +@param3 sample - hash-ref of a Sanger::CGP::Vcf::Sample objects representing samples to be included. + +@param3 options - hash-ref of options passed to generating command + +=cut +sub gen_header{ + my($self, $reference_path, $input_source, $samples, $options) = @_; + + my @process_logs = ( + Sanger::CGP::Vcf::VcfProcessLog->new( + -input_vcf_source => 'Pindel', + -input_vcf_ver => 'v02', # always have S2 at this point + ), + Sanger::CGP::Vcf::VcfProcessLog->new(-input_vcf_source => basename($0), + -input_vcf_ver => Sanger::CGP::Pindel->VERSION, + -input_vcf_param => $options, + ), + ); + + my @info = ( + {key => 'INFO', ID => 'PC', Number => 1, Type => 'String', Description => 'Pindel call'}, + {key => 'INFO', ID => 'RS', Number => 1, Type => 'Integer', Description => 'Range start'}, + {key => 'INFO', ID => 'RE', Number => 1, Type => 'Integer', Description => 'Range end'}, + {key => 'INFO', ID => 'LEN', Number => 1, Type => 'Integer', Description => 'Length'}, + {key => 'INFO', ID => 'REP', Number => 1, Type => 'Integer', Description => 'Change repeat count within range'}, + {key => 'INFO', ID => 'S1', Number => 1, Type => 'Integer', Description => 'S1'}, + {key => 'INFO', ID => 'S2', Number => 1, Type => 'Float', Description => 'S2'} + ); + + my @format = ( + {key => 'FORMAT', ID => 'GT', Number => 1, Type => 'String', Description => 'Genotype'}, + {key => 'FORMAT', ID => 'PP', Number => 1, Type => 'Integer', Description => 'Pindel calls on the positive strand'}, + {key => 'FORMAT', ID => 'NP', Number => 1, Type => 'Integer', Description => 'Pindel calls on the negative strand'}, + {key => 'FORMAT', ID => 'WTP', Number => 1, Type => 'Integer', Description => '+ve strand reads BLATed (or count for large deletions) to reference at this location'}, + {key => 'FORMAT', ID => 'WTN', Number => 1, Type => 'Integer', Description => '-ve strand reads BLATed (or count for large deletions) to reference at this location'}, + {key => 'FORMAT', ID => 'MTP', Number => 1, Type => 'Integer', Description => '+ve strand reads BLATed to alternate sequence at this location'}, + {key => 'FORMAT', ID => 'MTN', Number => 1, Type => 'Integer', Description => '-ve strand reads BLATed to alternate sequence at this location'}, + {key => 'FORMAT', ID => 'VAF', Number => 1, Type => 'Float', Description => 'Variant allele fraction using reads that unabiguously map to ref or alt seq (to 3 d.p.)'}, + ); + + my @blank_fmt = (q{.}) x (scalar @format -1); + $self->{_noread_gt} = join q{:}, './.', @blank_fmt; + + my $fmt_str = q{}; + for my $f(@format) { + $fmt_str .= q{:} if($fmt_str); + $fmt_str .= $f->{ID}; + } + $self->{_format} = $fmt_str; + + my $vcf = Vcf->new(version=>'4.1'); + my @timeData = localtime(time); + $vcf->add_header_line( { key => 'fileDate', value => sprintf '%d%02d%02d', 900 + $timeData[5], $timeData[4]+1, $timeData[3] } ); + $vcf->add_header_line( { key => 'source', value => $input_source }, 'append' => 1 ); + $vcf->add_header_line( { key => 'reference', value => $reference_path } ); + + for my $contig (@{$self->{_contigs}}){ + Sanger::CGP::Vcf::VcfUtil::add_vcf_contig($vcf,$contig) + } + + for my $inf (@info){ + $vcf->add_header_line($inf); + } + + for my $for (@format){ + $vcf->add_header_line($for); + } + + for my $process_log (@process_logs){ + Sanger::CGP::Vcf::VcfUtil::add_vcf_process_log($vcf,$process_log) + } + + for my $samp(sort keys %{$samples}) { + Sanger::CGP::Vcf::VcfUtil::add_vcf_sample($vcf, $samples->{$samp}, $samp); + } + + return $vcf->format_header(); +} + +sub gen_record{ + my($self, $record) = @_; + + # CHR POS ID REF ALT QUAL FILTER INFO FORMAT GENO GENO + + my $start = $record->start(); + $start-- if(substr($record->type(),0,1) eq 'D'); + + my $ret = $record->chro().$SEP; + $ret .= $start.$SEP; + $ret .= $record->id().$SEP; + + my $ref = uc ($record->lub . $record->ref_seq); + my $alt = uc ($record->lub . $record->alt_seq); + + $ret .= $ref.$SEP; + $ret .= $alt.$SEP; + $ret .= $record->sum_ms().$SEP; + $ret .= '.'.$SEP; + + # INFO + #PC=D;RS=19432;RE=19439;LEN=3;S1=4;S2=161.407;REP=2;PRV=1 + $ret .= 'PC='.$record->type().';'; + $ret .= 'RS='.$record->range_start().';'; + $ret .= 'RE='.$record->range_end().';'; + $ret .= 'LEN='.$record->length().';'; + $ret .= 'S1='.$record->s1().';'; + $ret .= 'S2='.$record->s2().';' if(defined $record->s2()); ## not presant in older versions of pindel + $ret .= 'REP='.$record->repeats().$SEP; + + # now attempt the blat stuff + my ($fh_target, $file_target) = tempfile( + DIR => '/dev/shm', + SUFFIX => '.fa', + UNLINK => 1 + ); + + my ($q_start, $q_end, $change_pos, $change_ref, $change_alt) = $self->blat_ref_alt($fh_target, $record); + + my $change_pos_low = $change_pos - 1; # coords are 0-based in blat output + my $range_l = ($record->range_end - $record->range_start) + 1; + my $change_pos_high = $change_pos_low + $range_l; # REF based range, adjusted in func + my $change_l = $record->end - $record->start + 1; + $self->{change_pos_low} = $change_pos_low; + $self->{change_pos_high} = $change_pos_high; + $self->{change_l} = $change_l; + $self->{change_ref} = $change_ref; + $self->{change_alt} = $change_alt; + $self->{q_start} = $q_start; + $self->{q_end} = $q_end; + $self->{file_target} = $file_target; + $self->{type} = $record->type; + $self->{chr} = $record->chro; + + # FORMAT + $ret .= $self->{_format}; + + for my $samp(@{$self->{_srt_samples}}) { + my ($wtp, $wtn, $mtp, $mtn) = $self->blat_reads($samp, $record); + $ret .= $SEP; + if($self->gen_all || exists $record->reads->{$samp}) { + $ret .= './.:'; + $ret .= $record->get_read_counts($samp, '+').q{:}; + $ret .= $record->get_read_counts($samp, '-').q{:}; + $ret .= $wtp.q{:}; + $ret .= $wtn.q{:}; + $ret .= $mtp.q{:}; + $ret .= $mtn.q{:}; + my $mtr = $mtp+$mtn; + my $depth = $wtp+$wtn+$mtr; + $ret .= $depth ? sprintf("%.3f", $mtr/$depth) : 0; + } + else { + $ret .= $self->{_noread_gt}; + } + } + $ret .= $NL; + return $ret; +} + +sub gen_all { + return shift->{_all}; +} + +sub hts_file_by_sample { + my ($self, $sample) = @_; + return $self->{_hts_set}->{$sample}; +} + +sub bas_by_sample { + my ($self, $sample) = @_; + return $self->{_bas_set}->{$sample}; +} + +sub ins_by_sample { + my ($self, $sample) = @_; + return $self->{_ins_set}->{$sample}; +} + +sub blat_reads { + my ($self, $sample, $record) = @_; + my ($fh_query, $file_query) = tempfile( + DIR => '/dev/shm', + SUFFIX => '.fa', + UNLINK => 1 + ); + close $fh_query or die "Failed to close $file_query (query reads)"; + my $read_buffer = $self->ins_by_sample($sample); + my $c_blat = sprintf $READS_AND_BLAT, $self->hts_file_by_sample($sample), $self->{chr}, $self->{q_start}-$read_buffer, $self->{q_end}+$read_buffer, $file_query, $self->{file_target}, $file_query; + my ($c_out, $c_err, $c_exit) = capture { system($c_blat); }; + if($c_exit) { + warn "An error occurred while executing $c_blat\n"; + warn "\tERROR$c_err\n"; + exit $c_exit; + } + +# print "target.fa\n"; +# system("cat $self->{file_target}"); +# ; +# print "query.fa\n"; +# system("cat $file_query"); +# ; +# print "\n$c_out\n"; + + my ($wtp, $wtn, $mtp, $mtn) = $self->blat_counts(\$c_out, $sample); + if($wtp + $wtn == 0) { + ($wtp, $wtn) = $self->sam_depth($sample, $record); + } + return ($wtp, $wtn, $mtp, $mtn); +} + +sub blat_counts { + my ($self, $blat) = @_; + $self->{read_map} = {}; +#$self->_dump_rec_detail; +#exit; + my @lines = split /\n/, ${$blat}; + for my $l(@lines) { + chomp $l; + $self->parse_deletion(\$l); + } + my (%wtr, %mtr); + for my $read(sort keys %{$self->{read_map}}) { + my $hits = scalar @{$self->{read_map}->{$read}}; + die "More than 2 hits!".Dumper($self->{read_map}->{$read}) if($hits > 2); + + # this handles prevents double counting should read 1&2 align across the same positiob + # also allows them to be counted if they go to ALT and REF + my $clean_read = $read; + $clean_read =~ s{/[12]$}{}; + + if($hits != 1 && $self->{read_map}->{$read}->[0]->{target} ne $self->{read_map}->{$read}->[1]->{target}) { + # this can occur due to differences outside the range we are testing, we've already checked the seq is as expected + # when it occurs increment "REF/WTR" + if($self->{read_map}->{$read}->[0]->{strand} ne $self->{read_map}->{$read}->[1]->{strand}) { + if(scalar keys %{$wtr{'+'}} <= scalar keys %{$wtr{'-'}}) { + $wtr{'+'}{$clean_read} = 1; + next; + } + $wtr{'-'}{$clean_read} = 1; + next; + } + $wtr{$self->{read_map}->{$read}->[0]->{strand}}{$clean_read} = 1; + next; + } + + if($self->{read_map}->{$read}->[0]->{target} eq 'REF') { + $wtr{$self->{read_map}->{$read}->[0]->{strand}}{$clean_read} = 1; + next; + } + $mtr{$self->{read_map}->{$read}->[0]->{strand}}{$clean_read} = 1; + } + return (scalar keys %{$wtr{'+'}}, scalar keys %{$wtr{'-'}}, scalar keys %{$mtr{'+'}}, scalar keys %{$mtr{'-'}}); +} + +sub sam_depth { + my ($self, $sample, $record) = @_; + my $mid_point = int ($record->range_start + (($record->range_end - $record->range_start)*0.5)); + my $c_samcount = sprintf $SAM_DEPTH_PN, $self->hts_file_by_sample($sample), $record->chro, $mid_point, $mid_point; +#warn $c_samcount; + my ($c_out, $c_err, $c_exit) = capture { system($c_samcount); }; + if($c_exit) { + warn "An error occurred while executing $c_samcount\n"; + warn "\tERROR$c_err\n"; + exit $c_exit; + } +# print $c_out; + return (split /\n/, $c_out); +} + +sub _dump_rec_detail { + my $self = shift; + for my $k(sort qw(change_pos_low change_pos_high change_l change_ref change_alt q_start q_end type chr)) { + printf "%s: %s\n", $k, ref $self->{$k} ? Dumper($self->{$k}) : $self->{$k}; + } + print "\n"; + return 0; +} + +sub parse_deletion { + my ($self, $rec) = @_; + #$self->_dump_rec_detail; + my @rec_d = split /\t/, ${$rec}; + # clean up trailing commas + $rec_d[$Q_SEQ] =~ s/,$//; + $rec_d[$T_SEQ] =~ s/,$//; + $self->{rec_d} = \@rec_d; + +#return 0 unless($rec_d[$Q_NAME] eq 'HX1_20016:2:1116:7791:42060/2'); + + # specific to deletion class + my $change_seq = $self->{change_ref}; + my $change_pos_high = $self->{change_pos_high}; + if($rec_d[$T_NAME] eq 'ALT') { + $change_pos_high -= $self->{change_l}; + $change_seq = $self->{change_alt}; + } + $self->{change_seq} = $change_seq; + + # all the reads that don't span the range + return 0 unless($rec_d[$T_START] <= $self->{change_pos_low} && $rec_d[$T_END] > $change_pos_high); + + my $result = {target => $rec_d[$T_NAME], strand => $rec_d[$STRAND], rec => $rec}; + if($self->gap_ok($change_pos_high)) { + push @{$self->{read_map}->{$rec_d[$Q_NAME]}}, $result; + return 1; + } + return 0; +} + +sub gap_ok { + my ($self, $change_pos_high) = @_; + my @b_sizes = split q{,}, $self->{rec_d}->[$BLOCK_SIZES]; + my @t_starts = split q{,}, $self->{rec_d}->[$T_STARTS]; + my $iter = @b_sizes - 1; + for my $i(0..$iter) { + if($t_starts[$i] <= $self->{change_pos_low} && $t_starts[$i] + $b_sizes[$i] > $change_pos_high && $self->match_ok($t_starts[$i])) { + return 1; + } + } + return 0; +} + +sub match_ok { + my ($self, $t_start) = @_; + # check the expected seq is found at the index of the low boudary + my $exp_index = $self->{change_pos_low} - $t_start; + return 0 if($exp_index < 0); + return 1 if(index($self->{rec_d}->[$Q_SEQ], $self->{change_seq}, $exp_index) == $exp_index); + return 0 if(index($self->{change_seq}, q{,}) >= 0); # don't allow gaps in the target for remaining checks + # allow for a minimal number of mismatches + my $change_len = length $self->{change_seq}; + my $q_exp = substr($self->{rec_d}->[$Q_SEQ], $exp_index, $change_len); + return 0 if(index($q_exp, q{,}) >= 0); # don't allow gaps in the query for remaining checks + return 0 if(length($q_exp) < $change_len); + my $mismatch = ($q_exp ^ $self->{change_seq}) =~ tr/\0//c; + return 1 if(($mismatch / $change_len) < 0.1); + return 0; +} + +sub blat_ref_alt { + my ($self, $fh, $record) = @_; + my $ref_left = $record->ref_left; + my $ref_right = $record->ref_right; + my $ref = q{}; + my $alt = q{}; # save an object lookup + my $change_at = length $ref_left; + + if($record->type eq 'D') { + $ref = uc $record->ref_seq; + #nothing to add for alt + } elsif($record->type eq 'I') { + #nothing to add for ref + $alt = $record->alt_seq; + $change_at -= 1; # force base before + } + else { # must be DI + $ref = $record->ref_seq; + $alt = $record->alt_seq; + } + my $q_start = $record->start - $change_at; # correcting for position handled in change_at + my $q_end = $record->end + length $ref_right; + + print $fh sprintf ">REF\n%s%s%s\n", $ref_left, $ref, $ref_right or die "Failed to write REF to blat ref temp file"; + print $fh sprintf ">ALT\n%s%s%s\n", $ref_left, $alt, $ref_right or die "Failed to write ALT to blat ref temp file"; + close $fh or die "Failed to close blat ref temp file"; + + my $seq_left = substr($ref_left, -1); + # -1 as includes the base before and after which would be -2 but need to correct for coord maths + # (for Del and Ins, unsure about DI at the moment) + my $seq_right = substr($ref_right, 0, ($record->range_end - $record->range_start) - 1); + + my $change_ref = lc ($seq_left.$ref.$seq_right); + my $change_alt = lc ($seq_left.$alt.$seq_right); + + return $q_start, $q_end, $change_at, $change_ref, $change_alt; +} diff --git a/perl/t/outputGenPindelRecordParser.t b/perl/t/outputGenPindelRecordParser.t index f862510..de82bba 100644 --- a/perl/t/outputGenPindelRecordParser.t +++ b/perl/t/outputGenPindelRecordParser.t @@ -470,7 +470,9 @@ subtest 'Object funcions' => sub { ['EAS139_64:1:55:1728:1427_r2_D0',16,22,16060468,29,'12M6D63M','*',0,0,'AGTTAACTCTCTTTTTTCTTTTTCTTTTTCTTTTTCTTTTTCTTTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTT','*','MD:Z:1A10^TTTTTC63','NM:i:7']]}}, -lub => 'T', -min_change => lc'TTTTTC', - -repeats => 7 + -repeats => 7, + -ref_left => 'GAGACCTCCCCAGAAATGGATGCCAGCATTATGCTTCCTATACAGCCTGCAGAACCATGAGCCAATTAACTCTCT', + -ref_right => 'TTTTTCTTTTTCTTTTTCTTTTTCTTTTTCTTTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCT', ); $obj->_parse_alignment($record_1,$alignments_1,\$header_string_1); @@ -514,7 +516,9 @@ subtest 'Object funcions' => sub { ['EAS131_6:8:80:742:1825_r2_D0',16,22,16060477,60,'45M30D30M','*',0,0,'TCTTTTTTCTTTTTCTTTTTCTTTTTCTTTTTCTTTTTCTTTTTCTCTTTCTTTCTTTCTTTCTTTCTTTCTTTC','*','MD:Z:45^TTTCTTTCTTTCTTTCTTTCTTTCTTTCTT29T0','NM:i:31']]}}, -lub => 'C', -min_change => 'TTTCTTTCTTTCTTTCTTTCTTTCTTTCTT', - -repeats => 1 # The minimum repeat is not actually repeated within the local vicinity of the event, despite the region being a repeat-region. + -repeats => 1, # The minimum repeat is not actually repeated within the local vicinity of the event, despite the region being a repeat-region. + -ref_left => 'CAGCCTGCAGAACCATGAGCCAATTAACTCTCTTTTTTCTTTTTCTTTTTCTTTTTCTTTTTCTTTTTCTTTTTC', + -ref_right => 'TCTTTCTTTCTTTCTTTCTTTCTTTCTTTTTGTTTTCTTTCATCTTTCCTTCTTCTTTTTT', ); $obj->_parse_alignment($record_2,$alignments_2,\$header_string_2); diff --git a/setup.sh b/setup.sh index be249b9..6b17836 100755 --- a/setup.sh +++ b/setup.sh @@ -21,28 +21,6 @@ # along with this program. If not, see . ########## LICENCE ########## -done_message () { - if [ $? -eq 0 ]; then - echo " done." - if [ "x$1" != "x" ]; then - echo $1 - fi - else - echo " failed. See output for error messages." $2 - echo " Please check INSTALL file for items that should be installed by a package manager" - exit 1 - fi -} - -get_file () { -# output, source - if hash curl 2>/dev/null; then - curl -sS -o $1 -L $2 - else - wget -nv -O $1 $2 - fi -} - if [[ ($# -ne 1 && $# -ne 2) ]] ; then echo "Please provide an installation path and optionally perl lib paths to allow, e.g." echo " ./setup.sh /opt/myBundle" @@ -57,21 +35,15 @@ if [[ $# -eq 2 ]] ; then CGP_PERLLIBS=$2 fi +# ALL tool versions used by opt-build.sh +# need to keep in sync with Dockerfile +export VER_CGPVCF="v2.2.1" +export VER_VCFTOOLS="0.1.16" +export VER_BLAT="v385" + # get current directory INIT_DIR=`pwd` -# information about this system -echo '============== System information ====' -set -x -lsb_release -a -uname -a -sw_vers -system_profiler -grep MemTotal /proc/meminfo -set +x -echo - - set -e # cleanup inst_path @@ -93,65 +65,8 @@ fi #add bin path for install tests export PATH=$INST_PATH/bin:$PATH -#create a location to build dependencies -SETUP_DIR=$INIT_DIR/install_tmp -mkdir -p $SETUP_DIR - -cd $SETUP_DIR - -## grab cpanm and stick in workspace, then do a self upgrade into bin: -get_file $SETUP_DIR/cpanm https://cpanmin.us/ -perl $SETUP_DIR/cpanm -l $INST_PATH App::cpanminus -CPANM=`which cpanm` -echo $CPANM - -PCAP=`perl -le 'eval "require $ARGV[0]" and print $ARGV[0]->VERSION' PCAP` -if [[ "x$PCAP" == "x" ]] ; then - echo "PREREQUISITE: Please install PCAP-core before proceeding:" - echo " https://github.com/cancerit/PCAP-core/releases" - exit 1; -fi - - -CGPVCF=`perl -le 'eval "require $ARGV[0]" and print $ARGV[0]->VERSION' Sanger::CGP::Vcf` -if [[ "x$CGPVCF" == "x" ]] ; then - echo "PREREQUISITE: Please install cgpVcf before proceeding:" - echo " https://github.com/cancerit/cgpVcf/releases" - exit 1; -fi - -echo -n "Compiling pindel binaries ..." -cd $INIT_DIR -g++ -O3 -o $SETUP_DIR/pindel c++/pindel.cpp && -g++ -O3 -o $SETUP_DIR/filter_pindel_reads c++/filter_pindel_reads.cpp && -cp $SETUP_DIR/pindel $INST_PATH/bin/. && -cp $SETUP_DIR/filter_pindel_reads $INST_PATH/bin/. && -# convenience for testing -mkdir -p $INIT_DIR/bin && -cp $SETUP_DIR/pindel $INIT_DIR/bin/. && -cp $SETUP_DIR/filter_pindel_reads $INIT_DIR/bin/. -done_message "" "Failed during compilation of pindel." - -cd $INIT_DIR/perl - -echo -n "Installing Perl prerequisites ..." -if ! ( perl -MExtUtils::MakeMaker -e 1 >/dev/null 2>&1); then - echo - echo "WARNING: Your Perl installation does not seem to include a complete set of core modules. Attempting to cope with this, but if installation fails please make sure that at least ExtUtils::MakeMaker is installed. For most users, the best way to do this is to use your system's package manager: apt, yum, fink, homebrew, or similar." -fi - -$CPANM -v --mirror http://cpan.metacpan.org -notest -l $INST_PATH/ --installdeps . < /dev/null -done_message "" "Failed during installation of core dependencies." - -echo -n "Installing cgpPindel ..." -perl Makefile.PL INSTALL_BASE=$INST_PATH && -make && -make test && -make install -done_message "" "cgpPindel install failed." - -# cleanup all junk -rm -rf $SETUP_DIR +bash build/opt-build.sh $INST_PATH +bash build/opt-build-local.sh $INST_PATH echo echo From 3251131a9aaa0cebb7f2e83a1c29b9109f3eb176 Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Tue, 21 Apr 2020 10:47:48 +0000 Subject: [PATCH 04/60] Simple del/ins "working", but looks like switch in parsing format is needed --- perl/bin/pindelCohort_to_vcf.pl | 2 +- .../Pindel/OutputGen/VcfCohortConverter.pm | 78 ++++++++++++++----- 2 files changed, 59 insertions(+), 21 deletions(-) diff --git a/perl/bin/pindelCohort_to_vcf.pl b/perl/bin/pindelCohort_to_vcf.pl index 0e0048d..3e5f228 100644 --- a/perl/bin/pindelCohort_to_vcf.pl +++ b/perl/bin/pindelCohort_to_vcf.pl @@ -76,7 +76,7 @@ sub process_pindel_file { my $records = 0; while(my $record = $prp->next_record) { #next unless($record->idx eq 'D36'); -last if($records > 40); +#last if($records > 40); print $out_fh $record_converter->gen_record($record); $records++; #die "told to exit for testing"; diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfCohortConverter.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfCohortConverter.pm index 3e0fea0..180378d 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfCohortConverter.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfCohortConverter.pm @@ -59,6 +59,8 @@ const my $T_STARTS => 20; const my $Q_SEQ => 21; const my $T_SEQ => 22; +const my $SD_MULT => 2; + 1; sub new{ @@ -91,8 +93,8 @@ sub _max_inserts { for my $s(keys %bas) { my $max_ins = 0; for my $rg($bas{$s}->read_groups) { - my $m_sd2 = $bas{$s}->get($rg, 'mean_insert_size') + ($bas{$s}->get($rg, 'insert_size_sd') * 2); - $max_ins = $m_sd2 if($m_sd2 > $max_ins); + my $m_sd = $bas{$s}->get($rg, 'mean_insert_size') + ($bas{$s}->get($rg, 'insert_size_sd') * $SD_MULT); + $max_ins = $m_sd if($m_sd > $max_ins); } $ins_by_sample{$s} = $max_ins; } @@ -226,7 +228,8 @@ sub gen_record{ my ($q_start, $q_end, $change_pos, $change_ref, $change_alt) = $self->blat_ref_alt($fh_target, $record); - my $change_pos_low = $change_pos - 1; # coords are 0-based in blat output + my $change_pos_low = $change_pos; + $change_pos_low-- if($record->type eq 'D'); my $range_l = ($record->range_end - $record->range_start) + 1; my $change_pos_high = $change_pos_low + $range_l; # REF based range, adjusted in func my $change_l = $record->end - $record->start + 1; @@ -321,17 +324,19 @@ sub blat_reads { sub blat_counts { my ($self, $blat) = @_; $self->{read_map} = {}; -#$self->_dump_rec_detail; -#exit; my @lines = split /\n/, ${$blat}; for my $l(@lines) { - chomp $l; - $self->parse_deletion(\$l); +#next unless($l =~ m/HX1_20016:2:1214:9678:44450/); +#print $l."\n"; + $self->parse_event(\$l); } my (%wtr, %mtr); for my $read(sort keys %{$self->{read_map}}) { my $hits = scalar @{$self->{read_map}->{$read}}; - die "More than 2 hits!".Dumper($self->{read_map}->{$read}) if($hits > 2); + if($hits > 2) { + print "More than 2 hits!".Dumper($self->{read_map}->{$read}); + exit 1; + } # this handles prevents double counting should read 1&2 align across the same positiob # also allows them to be counted if they go to ALT and REF @@ -366,14 +371,12 @@ sub sam_depth { my ($self, $sample, $record) = @_; my $mid_point = int ($record->range_start + (($record->range_end - $record->range_start)*0.5)); my $c_samcount = sprintf $SAM_DEPTH_PN, $self->hts_file_by_sample($sample), $record->chro, $mid_point, $mid_point; -#warn $c_samcount; my ($c_out, $c_err, $c_exit) = capture { system($c_samcount); }; if($c_exit) { warn "An error occurred while executing $c_samcount\n"; warn "\tERROR$c_err\n"; exit $c_exit; } -# print $c_out; return (split /\n/, $c_out); } @@ -386,17 +389,23 @@ sub _dump_rec_detail { return 0; } -sub parse_deletion { +sub parse_event { my ($self, $rec) = @_; - #$self->_dump_rec_detail; + my @rec_d = split /\t/, ${$rec}; + if(exists $self->{read_map}->{$rec_d[$Q_NAME]}) { + my @recs = @{$self->{read_map}->{$rec_d[$Q_NAME]}}; + for my $r(@recs) { + if($r->{'target'} eq $rec_d[$T_NAME] && $r->{'strand'} eq $rec_d[$STRAND]) { + return 0; + } + } + } # clean up trailing commas $rec_d[$Q_SEQ] =~ s/,$//; $rec_d[$T_SEQ] =~ s/,$//; $self->{rec_d} = \@rec_d; -#return 0 unless($rec_d[$Q_NAME] eq 'HX1_20016:2:1116:7791:42060/2'); - # specific to deletion class my $change_seq = $self->{change_ref}; my $change_pos_high = $self->{change_pos_high}; @@ -406,19 +415,26 @@ sub parse_deletion { } $self->{change_seq} = $change_seq; +#my %lookup = map { $_ => 1 } (qw(HX1_20016:2:1101:2402:12683/2 HX1_20016:2:1102:20983:27363/2 HX1_20016:2:1106:7679:42745/1 HX1_20016:2:1110:29944:60290/1 HX1_20016:2:1120:29589:3348/2 HX1_20016:2:1210:9100:28681/2 HX1_20016:2:1218:22049:68271/1 HX1_20016:2:2122:17208:35977/2 HX1_20016:2:2205:16234:70574/2 HX1_20016:2:2210:28879:48177/2 HX1_20016:2:2216:17888:66056/2)); +#if(exists $lookup{$self->{rec_d}->[$Q_NAME]}) { +# print "$self->{rec_d}->[$Q_NAME]\n"; +#} +#else { return 0;} + # all the reads that don't span the range return 0 unless($rec_d[$T_START] <= $self->{change_pos_low} && $rec_d[$T_END] > $change_pos_high); my $result = {target => $rec_d[$T_NAME], strand => $rec_d[$STRAND], rec => $rec}; - if($self->gap_ok($change_pos_high)) { - push @{$self->{read_map}->{$rec_d[$Q_NAME]}}, $result; - return 1; - } + if($self->gap_ok($change_pos_high)) { + push @{$self->{read_map}->{$rec_d[$Q_NAME]}}, $result; + return 1; + } return 0; } sub gap_ok { my ($self, $change_pos_high) = @_; + my @b_sizes = split q{,}, $self->{rec_d}->[$BLOCK_SIZES]; my @t_starts = split q{,}, $self->{rec_d}->[$T_STARTS]; my $iter = @b_sizes - 1; @@ -435,15 +451,20 @@ sub match_ok { # check the expected seq is found at the index of the low boudary my $exp_index = $self->{change_pos_low} - $t_start; return 0 if($exp_index < 0); + return 1 if(index($self->{rec_d}->[$Q_SEQ], $self->{change_seq}, $exp_index) == $exp_index); return 0 if(index($self->{change_seq}, q{,}) >= 0); # don't allow gaps in the target for remaining checks # allow for a minimal number of mismatches my $change_len = length $self->{change_seq}; my $q_exp = substr($self->{rec_d}->[$Q_SEQ], $exp_index, $change_len); +#print "cs: $self->{change_seq}\n"; +#print "qe: $q_exp\n"; + + return 0 if( substr($self->{change_seq}, 0, 1) ne substr($q_exp, 0, 1) || substr( $self->{change_seq}, -1, 1) ne substr($q_exp, -1, 1)); return 0 if(index($q_exp, q{,}) >= 0); # don't allow gaps in the query for remaining checks return 0 if(length($q_exp) < $change_len); my $mismatch = ($q_exp ^ $self->{change_seq}) =~ tr/\0//c; - return 1 if(($mismatch / $change_len) < 0.1); + return 1 if(($mismatch / $change_len) < 0.2); return 0; } @@ -467,6 +488,7 @@ sub blat_ref_alt { $ref = $record->ref_seq; $alt = $record->alt_seq; } + my $q_start = $record->start - $change_at; # correcting for position handled in change_at my $q_end = $record->end + length $ref_right; @@ -477,10 +499,26 @@ sub blat_ref_alt { my $seq_left = substr($ref_left, -1); # -1 as includes the base before and after which would be -2 but need to correct for coord maths # (for Del and Ins, unsure about DI at the moment) - my $seq_right = substr($ref_right, 0, ($record->range_end - $record->range_start) - 1); + my $seq_right; + if($record->type eq 'D') { + $seq_right = substr($ref_right, 0, ($record->range_end - $record->range_start) - 1); + } + elsif($record->type eq 'I') { + $seq_right = substr($ref_right, 0, ($record->range_end - $record->range_start)); + } + my $change_ref = lc ($seq_left.$ref.$seq_right); my $change_alt = lc ($seq_left.$alt.$seq_right); +# printf "%s\n", $record->ref_left; +# printf "%s\n", $record->ref_seq; +# printf "%s\n", $record->alt_seq; +# printf "%s\n", $record->ref_right; +# printf "%s\n", $record->type; +# printf "%s\n", $change_ref; +# printf "%s\n", $change_alt; +# #exit; + return $q_start, $q_end, $change_at, $change_ref, $change_alt; } From 51296cee52a1d94f2c21a61404b0adfcaba517ff Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Wed, 6 May 2020 18:03:55 +0000 Subject: [PATCH 05/60] Initial pass at cohort pindel processing, no visualisation outputs (BAM/CRAM) just VCF --- build/opt-build.sh | 10 +- perl/Makefile.PL | 2 + perl/bin/pindelCohort.pl | 61 +- perl/bin/pindelCohort_to_vcf.pl | 45 +- perl/bin/pindel_blat_vaf.pl | 100 + perl/bin/pindel_vcfSortNsplit.pl | 72 + perl/lib/Sanger/CGP/Pindel/Implement.pm | 124 +- perl/lib/Sanger/CGP/Pindel/InputGen.pm | 4 +- .../Pindel/OutputGen/PindelRecordParser.pm | 1 + .../CGP/Pindel/OutputGen/VcfBlatAugment.pm | 485 +++ .../Pindel/OutputGen/VcfCohortConverter.pm | 311 +- perl/t/1_pm_compile.t | 32 +- perl/t/data/blat/D.vcf | 3385 +++++++++++++++++ perl/t/data/blat/DI.vcf | 3385 +++++++++++++++++ perl/t/data/blat/SI.vcf | 3385 +++++++++++++++++ perl/t/data/blat/chr10_1-23700.fa | 396 ++ perl/t/data/blat/chr10_1-23700.fa.fai | 1 + perl/t/data/blat/test.bam | Bin 0 -> 310622 bytes perl/t/data/blat/test.bam.bai | Bin 0 -> 27048 bytes perl/t/data/blat/test.bam.bas | 2 + perl/t/vcfBlatAugment.t | 140 + 21 files changed, 11710 insertions(+), 231 deletions(-) create mode 100755 perl/bin/pindel_blat_vaf.pl create mode 100755 perl/bin/pindel_vcfSortNsplit.pl create mode 100644 perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm create mode 100644 perl/t/data/blat/D.vcf create mode 100644 perl/t/data/blat/DI.vcf create mode 100644 perl/t/data/blat/SI.vcf create mode 100644 perl/t/data/blat/chr10_1-23700.fa create mode 100644 perl/t/data/blat/chr10_1-23700.fa.fai create mode 100644 perl/t/data/blat/test.bam create mode 100644 perl/t/data/blat/test.bam.bai create mode 100644 perl/t/data/blat/test.bam.bas create mode 100644 perl/t/vcfBlatAugment.t diff --git a/build/opt-build.sh b/build/opt-build.sh index 9ba6815..79cbd4a 100644 --- a/build/opt-build.sh +++ b/build/opt-build.sh @@ -75,6 +75,10 @@ if [ ! -e $SETUP_DIR/cgpVcf.success ]; then rm -rf distro.* distro/* touch $SETUP_DIR/cgpVcf.success fi - -curl -sSL http://hgdownload.cse.ucsc.edu/admin/exe/linux.x86_64.${VER_BLAT}/blat/blat > $INST_PATH/bin/blat -chmod ugo+x $INST_PATH/bin/blat +set -x +if [ ! -e $SETUP_DIR/ucscTools.success ]; then + curl -sSL http://hgdownload.cse.ucsc.edu/admin/exe/linux.x86_64.${VER_BLAT}/blat/blat > $INST_PATH/bin/blat + curl -sSL http://hgdownload.cse.ucsc.edu/admin/exe/linux.x86_64.${VER_BLAT}/pslPretty > $INST_PATH/bin/pslPretty + chmod ugo+x $INST_PATH/bin/blat $INST_PATH/bin/pslPretty + touch $SETUP_DIR/ucscTools.success +fi diff --git a/perl/Makefile.PL b/perl/Makefile.PL index fbdd389..988a5a6 100755 --- a/perl/Makefile.PL +++ b/perl/Makefile.PL @@ -37,6 +37,8 @@ WriteMakefile( bin/pindel_germ_bed.pl bin/pindelCohort.pl bin/pindelCohort_to_vcf.pl + bin/pindel_vcfSortNsplit.pl + bin/pindel_blat_vaf.pl )], PREREQ_PM => { 'Const::Fast' => 0.014, diff --git a/perl/bin/pindelCohort.pl b/perl/bin/pindelCohort.pl index f8eea80..a939c24 100644 --- a/perl/bin/pindelCohort.pl +++ b/perl/bin/pindelCohort.pl @@ -31,26 +31,33 @@ BEGIN use warnings FATAL => 'all'; use autodie qw(:all); use Const::Fast qw(const); +use File::Copy; +use File::Path qw(remove_tree make_path); use PCAP::Cli; use Sanger::CGP::Pindel::Implement; use Data::Dumper; -const my @VALID_PROCESS => qw(input pindel ???); -my %index_max = ( 'input' => -1, +const my %INDEX_MAX => ( + 'input' => -1, 'pindel' => -1, - '???' => 1, + 'parse' => 1, # reads all pout, makes raw-vcf and splits to even sized for blat + 'blat' => -1, + 'concat' => 1, ); +const my @VALID_PROCESS => keys %INDEX_MAX; { my $options = setup(); my $threads = PCAP::Threaded->new($options->{'threads'}); - &PCAP::Threaded::disable_out_err if(exists $options->{'index'}); + #&PCAP::Threaded::disable_out_err if(exists $options->{'index'}); # register any process that can run in parallel here $threads->add_function('input', \&Sanger::CGP::Pindel::Implement::input_cohort); $threads->add_function('pindel', \&Sanger::CGP::Pindel::Implement::pindel); + $threads->add_function('blat', \&Sanger::CGP::Pindel::Implement::blat); + # start processes here (in correct order obviously), add conditions for skipping based on 'process' option if(!exists $options->{'process'} || $options->{'process'} eq 'input') { @@ -63,6 +70,27 @@ BEGIN $jobs = $options->{'limit'} if(exists $options->{'limit'} && defined $options->{'limit'}); $threads->run($jobs, 'pindel', $options); } + if(!exists $options->{'process'} || $options->{'process'} eq 'parse') { + Sanger::CGP::Pindel::Implement::parse($options); + } + $options->{'split_files'} = Sanger::CGP::Pindel::Implement::split_files($options) unless(exists $options->{'split_files'}); + if(!exists $options->{'process'} || $options->{'process'} eq 'blat') { + my $jobs = scalar @{$options->{'split_files'}}; + $jobs = $options->{'limit'} if(exists $options->{'limit'} && defined $options->{'limit'}); + $threads->run($jobs, 'blat', $options); + } + if(!exists $options->{'process'} || $options->{'process'} eq 'concat') { + Sanger::CGP::Pindel::Implement::concat($options); + cleanup($options) unless($options->{'debug'}); + } +} + +sub cleanup { + my $options = shift; + my $tmpdir = $options->{'tmp'}; + move(File::Spec->catdir($tmpdir, 'logs'), File::Spec->catdir($options->{'outdir'}, 'logs')) || die $!; + remove_tree $tmpdir if(-e $tmpdir); + return 0; } sub index_check { @@ -74,14 +102,19 @@ sub index_check { my @valid_seqs = Sanger::CGP::Pindel::Implement::valid_seqs($opts); my $refs = scalar @valid_seqs; - my $max = $index_max{$opts->{'process'}}; - if($max==-1){ + my $max = $INDEX_MAX{$opts->{'process'}}; + if($max == -1){ if(exists $opts->{'limit'}) { $max = $opts->{'limit'} > $refs ? $refs : $opts->{'limit'}; } else { if($opts->{'process'} eq 'input') { $max = $max_files; - } else { + } + elsif($opts->{'process'} eq 'blat') { + $opts->{'split_files'} = Sanger::CGP::Pindel::Implement::split_files($opts); + $max = scalar @{$opts->{'split_files'}}; + } + else { $max = $refs; } } @@ -116,22 +149,18 @@ sub setup { =head1 pindelCohort.pl -Similar to pindel.pl but processes 1 or more samples. References to BAM can ber replaced with CRAM. +Similar to pindel.pl but processes 1 sample. References to BAM can be replaced with CRAM. =head1 SYNOPSIS -pindelCohort.pl [options] sample1.bam [sample2.bam sample3.bam...] +pindelCohort.pl [options] sample1.bam Required parameters: -outdir -o Folder to output result to. -reference -r Path to reference genome file *.fa[.gz] - -simrep -s Full path to tabix indexed simple/satellite repeats. - -filter -f VCF filter rules file (see FlagVcf.pl for details) - -genes -g Full path to tabix indexed coding gene footprints. - -unmatched -u Full path to tabix indexed gff3 of unmatched normal panel - - see pindel_np_from_vcf.pl Optional + -all Generate BLAT counts for all samples regardless of pindel call state. -seqtype -st Sequencing protocol, expect all input to match [WGS] -assembly -as Name of assembly in use - when not available in BAM header SQ line. @@ -139,12 +168,10 @@ =head1 SYNOPSIS - when not available in BAM header SQ line. -exclude -e Exclude this list of ref sequences from processing, wildcard '%' - comma separated, e.g. NC_007605,hs37d5,GL% - -badloci -b Tabix indexed BED file of locations to not accept as anchors + -badloci -b Tabix indexed BED file of locations to not accept as anchors or valid events - e.g. hi-seq depth from UCSC - -skipgerm -sg Don't output events with more evidence in normal BAM. -cpus -c Number of cores to use. [1] - recommend max 4 during 'input' process. - -softfil -sf VCF filter rules to be indicated in INFO field as soft flags -limit -l When defined with '-cpus' internally thread concurrent processes. - requires '-p', specifically for pindel/pin2vcf steps -debug -d Don't cleanup workarea on completion. diff --git a/perl/bin/pindelCohort_to_vcf.pl b/perl/bin/pindelCohort_to_vcf.pl index 3e5f228..620bfe5 100644 --- a/perl/bin/pindelCohort_to_vcf.pl +++ b/perl/bin/pindelCohort_to_vcf.pl @@ -31,6 +31,7 @@ use File::Basename; use Getopt::Long; +use Data::UUID; use Sanger::CGP::Pindel::Implement; use Sanger::CGP::Pindel::OutputGen::VcfCohortConverter; @@ -56,6 +57,7 @@ -hts_set => $hts_by_sample, -bas_set => $bas_by_sample, -all => $options->{'all'}, + -badloci => $options->{'badloci'}, ); my $input_source = basename($0). '_v'. Sanger::CGP::Pindel->VERSION; my $header = $record_converter->gen_header($options->{'reference'}, $input_source, $vcfsamp_by_sample, $options); @@ -64,6 +66,7 @@ for my $in_file(@{$options->{'input'}}) { process_pindel_file($options, $in_file, $out_fh, $record_converter); } + close $options->{'output'} if($options->{'output'} ne \*STDOUT); } sub process_pindel_file { @@ -72,14 +75,15 @@ sub process_pindel_file { -path => $pindel_file, -fai => Bio::DB::HTS::Fai->load($options->{'reference'}), -noreads => 1, + ); + my $uuid_gen = Data::UUID->new; my $records = 0; while(my $record = $prp->next_record) { -#next unless($record->idx eq 'D36'); + $record->id($uuid_gen->to_string($uuid_gen->create)); #last if($records > 40); print $out_fh $record_converter->gen_record($record); -$records++; -#die "told to exit for testing"; +#$records++; } } @@ -210,7 +214,8 @@ sub setup{ 'as|assembly:s' => \$opts{'assembly'}, 'sp|species=s{0,}' => \@{$opts{'species'}}, 'pp|parent:s' => \$opts{'pp'}, - 'a|all' => \$opts{'all'} + 'a|all' => \$opts{'all'}, + 'b|badloci:s' => \$opts{'badloci'}, ); if(defined $opts{'v'}){ @@ -222,12 +227,25 @@ sub setup{ pod2usage(-verbose => 2) if(defined $opts{'m'}); PCAP::Cli::file_for_reading('ref', $opts{'reference'}); - my $i=1; + my @full_inputs; for my $if(@{$opts{'input'}}) { - PCAP::Cli::file_for_reading(sprintf('input(%d)', $i++), $if); + if($if =~ s/%/*/g) { + push @full_inputs, glob $if; + } + else { + push @full_inputs, $if; + } } + for my $if(@full_inputs) { + PCAP::Cli::file_for_reading('input (possibly expanded)', $if); + } + $opts{'input'} = \@full_inputs; - $opts{'output'} = \*STDOUT unless($opts{'output'}); + if($opts{'output'}) { + open my $fh, '>', $opts{'output'}; + $opts{'output'} = $fh; + } + else { $opts{'output'} = \*STDOUT; } # add hts_files from the remains of @ARGV Sanger::CGP::Pindel::Implement::cohort_files(\%opts); @@ -240,7 +258,7 @@ sub setup{ =head1 NAME -pindelCohort_to_vcf.pl - Takes a raw Pindel file and a set of bam files and produces a vcf file. +pindelCohort_to_vcf.pl - Takes raw Pindel files and a set of bam files to produces a collated vcf file. =head1 SYNOPSIS @@ -248,12 +266,13 @@ =head1 SYNOPSIS Required parameters: -ref -r File path to the reference file used to provide the coordinate system. - -input -i Files to read in, repeatable + -input -i Files to read in, repeatable or '%' wildcard Optional parameters: -output -o File path to output to. Defaults to STDOUT. -all -a Generate VAF for all samples, even when not seen by Pindel. - + -badloci -b Tabix indexed BED file of locations reject as events + - e.g. hi-seq depth from UCSC -project -prj String representing the project data is from. -prot -p String representing the sequencing protocol (e.g. genomic, targeted, RNA-seq). -assembly -as Reference assembly name, used when not found in BAM/CRAM headers. @@ -271,7 +290,7 @@ =head1 OPTIONS =item B<-input> -File path(s) to read. Accepts only raw pindel files, repeat for multiple. +File path(s) to read. Accepts only raw pindel files, repeat or '%' as wildcard for multiple. =item B<-output> @@ -310,8 +329,6 @@ =head1 OPTIONS =head1 DESCRIPTION -B will attempt to generate a vcf file from a Pindel output file. - -For every variant called by Pindel a blat will be performed and the results merged into a single vcf record. +B will attempt to generate a vcf file from a set of Pindel output files. =cut diff --git a/perl/bin/pindel_blat_vaf.pl b/perl/bin/pindel_blat_vaf.pl new file mode 100755 index 0000000..ab86b1c --- /dev/null +++ b/perl/bin/pindel_blat_vaf.pl @@ -0,0 +1,100 @@ +#!/usr/bin/env perl + +use strict; +use warnings FATAL => 'all'; +use autodie qw(:all); +use Cwd qw(abs_path); +use Pod::Usage qw(pod2usage); +use FindBin qw($Bin); +use lib "$Bin/../lib"; +use Getopt::Long; + +use PCAP::Cli; +use Sanger::CGP::Pindel::OutputGen::VcfBlatAugment; + +{ + my $options = setup(); + my $augment = Sanger::CGP::Pindel::OutputGen::VcfBlatAugment->new( + input => $options->{input}, + ref => $options->{ref}, + ofh => $options->{output}, + hts_file => $options->{hts}, + ); + $augment->output_header; + $augment->process_records; +} + + +sub setup{ + my %opts = ( + 'cmd' => join(" ", $0, @ARGV), + ); + GetOptions( 'h|help' => \$opts{h}, + 'm|man' => \$opts{m}, + 'v|version' => \$opts{v}, + 'o|output:s' => \$opts{output}, + 'r|ref=s' => \$opts{ref}, + 'i|input=s' => \$opts{input}, + 'd|debug' => \$opts{debug}, + 'hts=s' => \$opts{hts}, + ); + + if(defined $opts{'v'}) { + printf "Version: %s\n", Sanger::CGP::Pindel::Implement->VERSION; + exit; + } + + pod2usage(-verbose => 1) if(defined $opts{h}); + pod2usage(-verbose => 2) if(defined $opts{m}); + + PCAP::Cli::file_for_reading('ref', $opts{ref}); + PCAP::Cli::file_for_reading('input', $opts{input}); + PCAP::Cli::file_for_reading('hts', $opts{hts}); + unless(-e $opts{hts}.'.bai' || -e $opts{hts}.'.csi' || -e $opts{hts}.'.cram') { + die "ERROR: Unable to find appropriate index file for $opts{hts}\n"; + } + unless(-e $opts{hts}.'.bas') { + die "ERROR: Unable to find *.bas file for $opts{hts}\n"; + } + + if($opts{'output'}) { + open my $fh, '>', $opts{output}; + $opts{output} = $fh; + } + else { $opts{output} = \*STDOUT; } + + return \%opts; +} + +__END__ + +=head1 NAME + +pindel_blat_vaf.pl - Takes a raw Pindel VCF and bam file to add accurate counts. + +=head1 SYNOPSIS + +pindel_blat_vaf.pl [options] SAMPLE.bam + + SAMPLE.bam should have co-located *.bai and *.bas files. + + Required parameters: + -ref -r File path to the reference file used to provide the coordinate system. + -input -i VCF file to read in. + -hts BAM/CRAM file for associated sample. + + Optional parameters: + -output -o File path to output to. Defaults to STDOUT. + + Other: + -help -h Brief help message. + -man -m Full documentation. + -version -v Prints the version number. + +=head1 DESCRIPTION + +B will attempt to generate a vcf with expanded counts and VAR. + +For every variant called by Pindel a blat will be performed and the results merged into a single vcf record. + +=cut diff --git a/perl/bin/pindel_vcfSortNsplit.pl b/perl/bin/pindel_vcfSortNsplit.pl new file mode 100755 index 0000000..a465cf6 --- /dev/null +++ b/perl/bin/pindel_vcfSortNsplit.pl @@ -0,0 +1,72 @@ +#!/usr/bin/env perl + +use strict; +use warnings FATAL => 'all'; +use autodie qw(:all); +use File::Basename; +use Capture::Tiny qw(capture); +use Const::Fast qw(const); +use File::Temp; +use File::Path qw(make_path); +use File::Spec::Functions; + +const my $USAGE => sprintf "USAGE: %s in.vcf \n", basename($0); +const my $SRT_COUNT => q{bash -c "set -o pipefail; (grep -B 100000 -m 1 '^#CHRO' %s && grep -v '^#' %s | sort -s -S 1G -k 1,1 -k 2,2n -k 4,4 -k 5,5) | tee %s | grep -cv '^#'"}; +const my $NO_HEAD_SPLIT => q{bash -c "set -o pipefail; grep -v '^#' %s | split -a 4 --additional-suffix=.vcf -l %d - %s"}; +const my $CAPTURE_HEADER => q{grep -B 100000 -m 1 '^#CHRO' %s}; + +if(@ARGV < 3) { + die $USAGE; +} + +my ($in_vcf, $split_lines, $outdir) = @ARGV; + +die "ERROR: Absent or Empty file: $in_vcf" unless(-e $in_vcf && -s _ > 0); + +make_path($outdir) unless(-e $outdir); + +my $tmp_dir = File::Temp->newdir(DIR=> $outdir, CLEANUP => 1); +my $srt_vcf = catfile($tmp_dir, 'srt.vcf'); + +# first we sort and capture the number of variants +my $c_srt_count = sprintf $SRT_COUNT, $in_vcf, $in_vcf, $srt_vcf; +my ($c_out, $c_err, $c_exit) = capture { system($c_srt_count); }; +if($c_exit > 1) { # allow 1 as could be 0 events to work with + warn "An error occurred while executing $c_srt_count\n"; + warn "\tERROR$c_err\n"; + exit $c_exit; +} +chomp $c_out; +my $events = $c_out; +die "ERROR: Did not get a count of events" if($events !~ m/^\d+$/); + +my $prefix = catfile($tmp_dir, 'split_'); +my $c_split = sprintf $NO_HEAD_SPLIT, $srt_vcf, $split_lines, $prefix; +($c_out, $c_err, $c_exit) = capture { system($c_split); }; +if($c_exit > 1) { # allow 1 as could be 0 events to work with + warn "An error occurred while executing $c_split\n"; + warn "\tERROR$c_err\n"; + exit $c_exit; +} + +my $c_header = sprintf $CAPTURE_HEADER, $in_vcf; +($c_out, $c_err, $c_exit) = capture { system($c_header); }; +if($c_exit > 0) { + warn "An error occurred while executing $c_split\n"; + warn "\tERROR$c_err\n"; + exit $c_exit; +} +my $vcf_head = $c_out; + +# now need to convert all the files to valid VCF: +opendir(my $dh, $tmp_dir) || die "Can't opendir $tmp_dir: $!"; +while (readdir $dh) { + next unless($_ =~ m/^split_[a-z]{4}\.vcf$/); + my $split_vcf = catfile($tmp_dir, $_); + my $vcf_out = catfile($outdir, $_); + open my $ofh, '>', $vcf_out; + print $ofh $vcf_head; + close $ofh; + system("cat $split_vcf >> $vcf_out"); +} +closedir $dh; diff --git a/perl/lib/Sanger/CGP/Pindel/Implement.pm b/perl/lib/Sanger/CGP/Pindel/Implement.pm index 285054a..3989fe2 100644 --- a/perl/lib/Sanger/CGP/Pindel/Implement.pm +++ b/perl/lib/Sanger/CGP/Pindel/Implement.pm @@ -37,6 +37,8 @@ use List::Util qw(first); use FindBin qw($Bin); use Getopt::Long; use Pod::Usage qw(pod2usage); +use File::Basename; +use File::Spec::Functions; use Sanger::CGP::Pindel; @@ -53,6 +55,8 @@ const my $PIN_MERGE => q{ -o %s -i %s -r %s}; const my $FLAG => q{ -a %s -u %s -s %s -i %s -o %s -r %s}; const my $PIN_GERM => q{ -f %s -i %s -o %s}; const my $BASE_GERM_RULE => 'F012'; # prefixed with additional F if fragment filtering. +const my $COHORT_2_VCF => q{ -r %s -i %s -o %s %s%s}; +const my $VCF_SPLIT_SIZE => 5_000; sub input_cohort{ my ($index, $options) = @_; @@ -212,6 +216,105 @@ sub pindel { return 1; } +sub parse { + my ($options) = @_; + my $tmp = $options->{'tmp'}; + + my $pout = File::Spec->catdir($tmp, 'pout'); + my $vcf = File::Spec->catdir($tmp, 'vcf'); + make_path($vcf) unless(-e $vcf); + my $collated_vcf = File::Spec->catfile($vcf, 'raw.vcf'); + + unless(PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), 'tovcf')) { + my $bad_loci = q{}; + if($options->{badloci}) { + $bad_loci = sprintf q{ -b %s }, $options->{badloci}; + } + my $command = $^X.' '._which('pindelCohort_to_vcf.pl'); + $command .= sprintf $COHORT_2_VCF, + $options->{'reference'}, + File::Spec->catfile($pout, '%_%_%'), + $collated_vcf, + $bad_loci, + join(q{ }, @{$options->{hts_files}}); + PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), $command, 'tovcf'); + PCAP::Threaded::touch_success(File::Spec->catdir($tmp, 'progress'), 'tovcf'); + } + unless(PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), 'split')) { + #perl pindel_vcfSortNsplit.pl run_PD26988a/tmpPindel/vcf/raw.vcf 10000 tsrt + my $command = $^X.' '._which('pindel_vcfSortNsplit.pl'); + $command .= sprintf q{ %s %d %s}, + $collated_vcf, + $VCF_SPLIT_SIZE, + $vcf; + PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), $command, 'split'); + PCAP::Threaded::touch_success(File::Spec->catdir($tmp, 'progress'), 'split'); + } + return 1; +} + +sub concat { + my ($options) = @_; + my $tmp = $options->{'tmp'}; + + my $vcf = File::Spec->catdir($tmp, 'vcf'); + my $sample_name = (PCAP::Bam::sample_name($options->{'hts_files'}->[0]))[0]; + my $vcf_gz = File::Spec->catfile($options->{'outdir'}, sprintf('%s.pindel.vcf.gz', $sample_name)); + unless(PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), 'concat')) { + #vcf-concat blat_*.vcf + my $command = _which('vcf-concat'); + $command .= sprintf q{ %s | bgzip -c > %s}, + File::Spec->catfile($vcf, 'blat_*.vcf'), + $vcf_gz; + PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), $command, 'concat'); + PCAP::Threaded::touch_success(File::Spec->catdir($tmp, 'progress'), 'concat'); + } + unless(PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), 'tabix')) { + my $command = _which('tabix'); + $command .= sprintf q{ -f -p vcf %s}, $vcf_gz; + PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), $command, 'tabix'); + PCAP::Threaded::touch_success(File::Spec->catdir($tmp, 'progress'), 'tabix'); + } + return 1; +} + +sub split_files { + my $options = shift; + my $vcf = File::Spec->catdir($options->{'tmp'}, 'vcf'); + my $patt = catfile($vcf, 'split_*.vcf'); + my @files = sort glob $patt; + return \@files; +} + +sub blat { + my ($index_in, $options) = @_; + my $tmp = $options->{'tmp'}; + my $vcf = File::Spec->catdir($tmp, 'vcf'); + # -i run_PD26988a/tmpPindel/vcf/raw.vcf + + return 1 if(exists $options->{'index'} && $index_in != $options->{'index'}); + + my @split_files = @{$options->{'split_files'}}; + my @indicies = limited_indicies($options, $index_in, scalar @split_files); + for my $index(@indicies) { + next if PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), $index); + + my $split_file = $split_files[$index-1]; + my $blat_file = $split_file; + $blat_file =~ s/split_([a-z]+)/blat_$1/; + my $command = $^X.' '._which('pindel_blat_vaf.pl'); + $command .= sprintf q{ -r %s -hts %s -i %s -o %s}, + $options->{'reference'}, + $options->{hts_files}->[0], + $split_file, + $blat_file; + + PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), $command, $index); + PCAP::Threaded::touch_success(File::Spec->catdir($tmp, 'progress'), $index); + } + return 1; +} + sub pindel_to_vcf { my ($index_in, $options) = @_; my $tmp = $options->{'tmp'}; @@ -486,9 +589,10 @@ sub _which { sub shared_setup { my ($are_paths, $extra_opts) = @_; + my $script_name = basename($0); my %opts; pod2usage(-msg => "\nERROR: Option must be defined.\n", -verbose => 1, -output => \*STDERR) if(scalar @ARGV == 0); - $opts{'cmd'} = join " ", $0, @ARGV; + $opts{'cmd'} = join " ", $script_name, @ARGV; my %load_opts = ( 'h|help' => \$opts{'h'}, 'm|man' => \$opts{'m'}, @@ -514,6 +618,8 @@ sub shared_setup { 'l|limit=i' => \$opts{'limit'}, 'd|debug' => \$opts{'debug'}, 'a|apid:s' => \$opts{'apid'}, + # specifically for cohort + 'all' => \$opts{'all'}, ); for my $opt_key(keys %{$extra_opts}) { my $v = $extra_opts->{$opt_key}; @@ -529,13 +635,17 @@ sub shared_setup { exit 0; } + if($script_name eq 'pindel.pl') { + PCAP::Cli::file_for_reading('simrep', $opts{'simrep'}); + PCAP::Cli::file_for_reading('filters', $opts{'filters'}); + PCAP::Cli::file_for_reading('genes', $opts{'genes'}); + PCAP::Cli::file_for_reading('unmatched', $opts{'unmatched'}); + PCAP::Cli::file_for_reading('softfil', $opts{'softfil'}) if(defined $opts{'softfil'}); + } + PCAP::Cli::file_for_reading('reference', $opts{'reference'}); - PCAP::Cli::file_for_reading('simrep', $opts{'simrep'}); - PCAP::Cli::file_for_reading('filters', $opts{'filters'}); - PCAP::Cli::file_for_reading('genes', $opts{'genes'}); - PCAP::Cli::file_for_reading('unmatched', $opts{'unmatched'}); - PCAP::Cli::file_for_reading('softfil', $opts{'softfil'}) if(defined $opts{'softfil'}); PCAP::Cli::out_dir_check('outdir', $opts{'outdir'}); + my $final_logs = File::Spec->catdir($opts{'outdir'}, 'logs'); if(-e $final_logs) { warn "NOTE: Presence of '$final_logs' directory suggests successful complete analysis, please delete to rerun\n"; @@ -545,7 +655,6 @@ sub shared_setup { delete $opts{'process'} unless(defined $opts{'process'}); delete $opts{'index'} unless(defined $opts{'index'}); delete $opts{'limit'} unless(defined $opts{'limit'}); - delete $opts{'exclude'} unless(defined $opts{'exclude'}); delete $opts{'badloci'} unless(defined $opts{'badloci'}); delete $opts{'apid'} unless(defined $opts{'apid'}); @@ -554,7 +663,6 @@ sub shared_setup { $opts{'threads'} = 1 unless(defined $opts{'threads'}); $opts{'seqtype'} = 'WGS' unless(defined $opts{'seqtype'}); - # make all things that appear to be paths complete (absolute not great if BAM/BAI in different locations) for my $key (keys %opts) { next unless( first {$key eq $_} (qw(reference outdir badloci simrep filters genes unmatched softfil), @{$are_paths}) ); diff --git a/perl/lib/Sanger/CGP/Pindel/InputGen.pm b/perl/lib/Sanger/CGP/Pindel/InputGen.pm index 8894305..9c30825 100644 --- a/perl/lib/Sanger/CGP/Pindel/InputGen.pm +++ b/perl/lib/Sanger/CGP/Pindel/InputGen.pm @@ -222,7 +222,7 @@ sub _process_set { } } -sub _tabix_to_interval_tree { +sub tabix_to_interval_tree { my $bed = shift; my %tree; my $z = IO::Uncompress::Gunzip->new($bed, MultiStream => 1) or die "gunzip failed: $GunzipError\n"; @@ -263,7 +263,7 @@ sub reads_to_pindel { my $tabix; if(defined $bed) { # was tabix, keeping name for consistency - $tabix = _tabix_to_interval_tree($bed); + $tabix = tabix_to_interval_tree($bed); } @reads = @{$reads[0]} if(ref $reads[0] eq 'ARRAY'); diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/PindelRecordParser.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/PindelRecordParser.pm index 39e62b8..3ccb057 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/PindelRecordParser.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/PindelRecordParser.pm @@ -516,6 +516,7 @@ between bwa and pindel. =cut sub _parse_read { my ($record, $chr, $start_pos, $read, $ref_seq_length, $read_idx, $change_ref_start, $change_ref_end, $_buffer_region, $_buffer_region_start, $no_read_data) = @_; + $no_read_data ||= 0; my @bits = split /\t+/, ${$read}; my $sample = $bits[-2]; diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm new file mode 100644 index 0000000..b8665a4 --- /dev/null +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm @@ -0,0 +1,485 @@ +package Sanger::CGP::Pindel::OutputGen::VcfBlatAugment; + +########## LICENCE ########## +# Copyright (c) 2020 Genome Research Ltd. +# +# Author: CASM IT +# +# This file is part of cgpPindel. +# +# cgpPindel is free software: you can redistribute it and/or modify it under +# the terms of the GNU Affero 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 Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +########## LICENCE ########## + +use strict; +use warnings FATAL => 'all'; +use autodie qw(:all); +use Const::Fast qw(const); +use File::Basename; +use List::Util qw(max); +use File::Temp qw(tempfile); +use Capture::Tiny qw(capture); +use Vcf; +use Bio::DB::HTS; +use Bio::DB::HTS::Faidx; +use Sanger::CGP::Pindel; +use Sanger::CGP::Vcf::VcfProcessLog; +use Sanger::CGP::PindelPostProcessing::VcfSoftFlagger; +use Sanger::CGP::Vcf::VcfUtil; +use PCAP::Bam::Bas; + +use Data::Dumper; + +const my $SD_MULT => 2; +const my $V_FMT => 8; +const my $V_GT => 9; +const my $READS_AND_BLAT => q{bash -c 'set -o pipefail ; samtools view -uF 3840 %s %s | samtools fasta - > %s && blat -t=dna -q=dna -noTrimA -minIdentity=95 -noHead -out=psl %s %s %s && pslPretty -long -axt %s %s %s /dev/stdout'}; +# returns +ve and then -ve results +const my $SAM_DEPTH_PN => q{bash -c "set -o pipefail ; samtools view -uF 3844 %s %s | pee 'samtools view -c -F 16 -' 'samtools view -c -f 16 -'"}; +const my $LOCI_FMT => '%s:%d-%d'; + +1; + +sub new{ + my $proto = shift; + my (%args) = @_; + my $class = ref($proto) || $proto; + + my $self = { + input => $args{input}, + ref => $args{ref}, + ofh => $args{ofh}, + hts_file => $args{hts_file}, + }; + bless $self, $class; + $self->_init; + + return $self; +} + +sub _init { + my $self = shift; + my $vcf = Vcf->new(file => $self->{input}); + $vcf->parse_header(); + $self->{vcf} = $vcf; + $self->_add_headers; + $self->_hts; + my $bas_sample = $self->_buffer_sizes; # has to go before validate sample + $self->_validate_sample($bas_sample); + # load the fai + $self->{fai} = Bio::DB::HTS::Faidx->new($self->{ref}); + return 1; +} + +sub _validate_sample { + my ($self, $bas_sample) = @_; + # check BAM/CRAM and VCF have same sample + + my $hts_sample; + foreach my $line (split(/\n/,$self->{hts}->header->text)) { + next unless($line =~ m/^\@RG/); + chomp $line; + ($hts_sample) = $line =~ m/SM:([^\t]+)/; + last if(defined $hts_sample); + } + my @samples = $self->{vcf}->get_samples(); + die sprintf "ERROR: Only expecting 1 sample in VCF '%s', got %d\n", $self->{vcf}, scalar @samples if(@samples > 1); + die sprintf "ERROR: Sample mismatch between BAM/CRAM (%s) and VCF (%s)\n", $hts_sample, $samples[0] if($hts_sample ne $samples[0]); + die sprintf "ERROR: Sample mismatch between BAM/CRAM (%s) and BAS (%s)\n", $hts_sample, $bas_sample if($hts_sample ne $bas_sample); + $self->{sample} = $hts_sample; +} + +sub _buffer_sizes { + my $self = shift; + ## Set the max read length and insert size + SD*$SD_MULTI using the bas file data + my $b = PCAP::Bam::Bas->new($self->{hts_file}.'.bas'); + my $max_ins = 0; + my $max_rl = 0; + my $sample; + for my $rg($b->read_groups) { + my $m_sd = $b->get($rg, 'mean_insert_size') + ($b->get($rg, 'insert_size_sd') * $SD_MULT); + $max_ins = $m_sd if($m_sd > $max_ins); + my $rl = max ($b->get($rg, 'read_length_r1'), $b->get($rg, 'read_length_r2')); + $max_rl = $rl if($rl > $max_rl); + my $s = $b->get($rg, 'sample'); + if($sample) { + die "ERROR: Multiple samples found in %s\n", $self->{hts}.'.bas' if($sample ne $s) + } + else { + $sample = $s; + } + } + $self->{max_insert} = $max_ins; + $self->{max_rl} = $max_rl; + return $sample; +} + +sub _hts { + my $self = shift; + $self->{hts} = Bio::DB::HTS->new(-bam => $self->{hts_file}, -fasta => $self->{ref}); + return 1; +} + +sub output_header { + my $self = shift; + my $fh = $self->{ofh}; + print $fh Sanger::CGP::PindelPostProcessing::VcfSoftFlagger::reformat_header($self->{vcf}); +} + +sub _add_headers { + my $self = shift; + my $vcf = $self->{'vcf'}; + + + my %options = ( + input => basename($self->{input}), + ref => basename($self->{ref}), + # probably more if we expose cutoffs + ); + Sanger::CGP::Vcf::VcfUtil::add_vcf_process_log($vcf, + Sanger::CGP::Vcf::VcfProcessLog->new( + -input_vcf_source => basename($0), + -input_vcf_ver => Sanger::CGP::Pindel->VERSION, + -input_vcf_param => \%options, + ) + ); + + $vcf->add_header_line({'key'=>'source', 'value' => basename($0)}, 'append' => 1); + + my @format = ( + {key => 'FORMAT', ID => 'WTP', Number => 1, Type => 'Integer', Description => '+ve strand reads BLATed (or count for large deletions) to reference at this location'}, + {key => 'FORMAT', ID => 'WTN', Number => 1, Type => 'Integer', Description => '-ve strand reads BLATed (or count for large deletions) to reference at this location'}, + {key => 'FORMAT', ID => 'MTP', Number => 1, Type => 'Integer', Description => '+ve strand reads BLATed to alternate sequence at this location'}, + {key => 'FORMAT', ID => 'MTN', Number => 1, Type => 'Integer', Description => '-ve strand reads BLATed to alternate sequence at this location'}, + {key => 'FORMAT', ID => 'VAF', Number => 1, Type => 'Float', Description => 'Variant allele fraction using reads that unambiguously map to ref or alt seq (to 3 d.p.)'}, + ); + $self->{fmt_ext} = q{}; + for my $f(@format) { + $vcf->add_header_line($f); + $self->{fmt_ext} .= q{:}.$f->{ID}; + } +} + +sub to_data_hash { + my ($self, $v_d) = @_; + my @items = @{$v_d}; + my %out; + # simplified version of Vcf->next_data_hash + # only stuff we need + $out{CHROM} = $items[0]; + $out{POS} = $items[1]; + # trim the first base from these + $out{REF} = substr $items[3], 1; + $out{ALT} = substr $items[4], 1; + + + # parse the info block + for my $info (split(/;/,$items[7])) { + my ($key,$val) = split(/=/,$info); + # all the values we need are key/val + next unless(defined $val); + die "Clash between INFO and columns" if(exists $out{$key}); + $out{$key} = $val; + } + + # add END + $out{END} = $out{POS}; + if($out{PC} ne 'I') { # so D/DI + $out{END} += $out{LEN} + 1; + } + else { + $out{END} += 1; + } +#print "$out{POS} - $out{END}\n"; +#exit; + + # skip FORMAT + # parse GT + my ($gt, $pp, $pn) = split /:/, $items[9]; + # put in top level as no clash + $out{PP} = $pp; + $out{PN} = $pn; + return \%out; +} + +sub process_records { + my $self = shift; + my $fh = $self->{ofh}; +my $count=0; + while(my $v_d = $self->{vcf}->next_data_array) { +$count++; +#next if($count != 8); # important the one relating to the bug +#next if($count != 21247); +#printf "%s\n", join "\t", @{$v_d}; + my $v_h = $self->to_data_hash($v_d); +#next if($v_h->{'POS'} != 49092625); + my @extra_gt = $self->blat_record($v_h); + $v_d->[$V_FMT] .= $self->{fmt_ext}; + $v_d->[$V_GT] = join q{:}, $v_d->[$V_GT], @extra_gt; + printf $fh "%s\n", join "\t", @{$v_d}; +#last; +#last if($count == 250); + } +} + +sub blat_record { + my ($self, $v_h) = @_; + # now attempt the blat stuff + my ($fh_target, $file_target) = tempfile( DIR => '/dev/shm', SUFFIX => '.fa', UNLINK => 1 ); + $self->blat_ref_alt($fh_target, $v_h); + close $fh_target or die "Failed to close blat ref temp file"; + + my $change_pos_low = $v_h->{change_pos}; + $change_pos_low++ if($v_h->{PC} eq 'I'); + my $range_l = ($v_h->{RE} - $v_h->{RS}) + 1; + my $change_pos_high = $change_pos_low + $range_l; # REF based range, adjusted in func + $v_h->{change_pos_low} = $change_pos_low; + $v_h->{change_pos_high} = $change_pos_high; + + return $self->blat_reads($v_h, $file_target); +} + +sub read_ranges { + my ($self, $v_h) = @_; + # return a string of chr:s-e... if approprate. + my $ret_val; + my ($chr, $q_start, $q_end) = ($v_h->{CHROM}, $v_h->{q_start}, $v_h->{q_end}); + my $read_buffer = $self->{max_insert}; + if($q_start + ($read_buffer * 2) > $q_end) { + $ret_val = sprintf $LOCI_FMT, $chr, $q_start - $read_buffer, $q_end + $read_buffer; + } + else { + $ret_val = sprintf $LOCI_FMT, $chr, $q_start - $read_buffer, $q_start + $read_buffer; + $ret_val .= q{ }; + $ret_val .= sprintf $LOCI_FMT, $chr, $q_end - $read_buffer, $q_end + $read_buffer + } +#print "$ret_val\n"; + return $ret_val; +} + +sub blat_reads { + my ($self, $v_h, $file_target) = @_; + # setup ther temp files + my ($fh_query, $file_query) = tempfile( DIR => '/dev/shm', SUFFIX => '.fa', UNLINK => 1); + close $fh_query or die "Failed to close $file_query (query reads)"; + my ($fh_psl, $file_psl) = tempfile(DIR => '/dev/shm', SUFFIX => '.psl', UNLINK => 1); + close $fh_psl or die "Failed to close $file_psl (psl output)"; + + my $c_blat = sprintf $READS_AND_BLAT, $self->{hts_file}, $self->read_ranges($v_h), $file_query, $file_target, $file_query, $file_psl, $file_psl, $file_target, $file_query; +#print "$c_blat\n"; + my ($c_out, $c_err, $c_exit) = capture { system($c_blat); }; + if($c_exit) { + warn "An error occurred while executing $c_blat\n"; + warn "\tERROR$c_err\n"; + exit $c_exit; + } + +# print "query.fa\n"; +#system("cat $file_query"); +# print "target.fa\n"; +#system("cat $file_target"); +#print "\n$c_out\n"; +# exit 1; + + my ($wtp, $wtn, $mtp, $mtn) = $self->psl_axt_parser(\$c_out, $v_h); + if($wtp + $wtn == 0) { + ($wtp, $wtn) = $self->sam_depth($v_h); + } + my $mtr = $mtp+$mtn; + my $depth = $wtp+$wtn+$mtr; + my $vaf = $depth ? sprintf("%.3f", $mtr/$depth) : 0; + return ($wtp, $wtn, $mtp, $mtn, $vaf); +} + +sub psl_axt_parser { + my ($self, $blat_axt, $v_h) = @_; + # collate the data by readname and order by score + my @lines = split /\n/, ${$blat_axt}; + my $line_c = @lines; + # group all reads and order by score + my %reads; + for(my $i = 0; $i<$line_c; $i+=4) { + my ($id, $t_name, $t_start, $t_end, $q_name, $q_start, $q_end, $strand, $score) = split q{ }, $lines[$i]; + push @{$reads{$q_name}{$score}}, [$t_name, $t_start, $t_end, $q_name, $q_start, $q_end, $strand, $score, $lines[$i+1], $lines[$i+2]]; + } + +#print Dumper(\%reads); +my $SKIP_EVENT = q{}; + + my %TYPE_STRAND; + # sort keys for consistency + READ: for my $read(sort keys %reads) { + # get the alignment with the highest score + for my $score(sort {$b<=>$a} keys %{$reads{$read}}) { + my @records = @{$reads{$read}{$score}}; + if(@records != 1) { # if best score has more than one alignment it is irrelevant +#print Dumper(\@records); + next READ; + } + my $record = $records[0]; + if($self->parse_axt_event($v_h, $record) == 1) { + $TYPE_STRAND{$record->[0].$record->[6]} += 1; + } +#$SKIP_EVENT = unless($SKIP_EVENT eq q{1}); +#chomp $SKIP_EVENT; + next READ; # remaining items are worse alignments + } + } + my $wtp = $TYPE_STRAND{'REF+'} || 0; + my $wtn = $TYPE_STRAND{'REF-'} || 0; + my $mtp = $TYPE_STRAND{'ALT+'} || 0; + my $mtn = $TYPE_STRAND{'ALT-'} || 0; + return ($wtp, $wtn, $mtp, $mtn); +} + +sub parse_axt_event { + my ($self, $v_h, $rec) = @_; + my ($t_name, $t_start, $t_end, $q_name, $q_start, $q_end, $strand, $score, $t_seq, $q_seq) = @{$rec}; + + # specific to deletion class + my $change_seq = $v_h->{change_ref}; + my $change_pos_high = $v_h->{change_pos_high}; + if($t_name eq 'ALT') { + $change_pos_high -= $v_h->{LEN}; + $change_seq = $v_h->{change_alt}; + } + +# eval { +# my $exp_pos = $v_h->{change_pos_low} - $t_start; # duplicate of if block code +# print join "\t", $t_name, $t_start, $t_end, $q_name, $q_start, $q_end, $strand, $score; +# printf "\n%s\n%s\n", $t_seq, $q_seq; +# print "exp_pos: $exp_pos\n"; +# printf "q_seqlen: %s\n", length $q_seq; +# my $sub_q_seq = substr($q_seq, $exp_pos, length $change_seq); +# my $lpad = q{ } x $exp_pos; +# print $lpad.$change_seq."\n"; +# print $lpad.$sub_q_seq."\n"; +# my $boo = $t_start <= $v_h->{change_pos_low} && $t_end > $change_pos_high; +# print "$boo: $t_start <= $v_h->{change_pos_low} && $t_end > $change_pos_high\n"; + +# print "INDEX: ".index($q_seq, $change_seq, $exp_pos)."\n"; +# }; print "OUT OF BOUNDS\n" if $@; + + + # all the reads that don't span the range + my $retval = 0; + if($t_start <= $v_h->{change_pos_low} && $t_end > $change_pos_high) { + # look for the change (or absence) where we expect it + my $exp_pos = $v_h->{change_pos_low} - $t_start; + if($exp_pos <= length $q_seq) { + my $sub_q_seq = substr($q_seq, $exp_pos, length $change_seq); + if(length $change_seq == length $sub_q_seq # same length + && index($q_seq, q{-}) == -1 # no gaps + && substr($change_seq,0,1) eq substr($sub_q_seq,0,1) # matching first base + && substr($change_seq,-1,1) eq substr($sub_q_seq,-1,1) # matching last base + ) { + $retval = 1; + } + } + } +#print "KEEP: $retval\n"; + return $retval; +} + +sub sam_depth { + my ($self, $v_h) = @_; + my $mid_point = int ($v_h->{RS} + (($v_h->{RE} - $v_h->{RS})*0.5)); + my $read_search = sprintf $LOCI_FMT, $v_h->{CHROM}, $mid_point, $mid_point; + my $c_samcount = sprintf $SAM_DEPTH_PN, $self->{hts_file}, $read_search; + my ($c_out, $c_err, $c_exit) = capture { system($c_samcount); }; + if($c_exit) { + warn "An error occurred while executing $c_samcount\n"; + warn "\tERROR$c_err\n"; + exit $c_exit; + } + return (split /\n/, $c_out); +} + +sub flanking_ref { + my ($self, $v_h) = @_; + # use max_rl to extend before and after the range_start/end + my $ref_left = $self->{fai}->get_sequence_no_length( + sprintf $LOCI_FMT, + $v_h->{CHROM}, + ($v_h->{POS} - $self->{max_rl})+1, + $v_h->{POS}, + ); +#print "$ref_left\n"; + + my $ref_right = $self->{fai}->get_sequence_no_length( + sprintf $LOCI_FMT, + $v_h->{CHROM}, + $v_h->{END}, + $v_h->{END} + $self->{max_rl}, + ); +#print "$ref_right\n"; + return [$ref_left, $ref_right] +} + +sub blat_ref_alt { + my ($self, $fh, $v_h) = @_; + my ($ref_left, $ref_right) = @{$self->flanking_ref($v_h)}; + my $ref = $v_h->{REF}; + my $alt = $v_h->{ALT}; + my $r_start = $v_h->{RS}; + my $r_end = $v_h->{RE}; + my $change_at = length $ref_left; + + my $call_type = $v_h->{PC}; + # THIS MAY NOT BE CORRECT NOW + if($call_type eq 'I') { + $change_at -= 1; # force base before + } + + # used when getting reads from HTSfile + my $q_start = $v_h->{POS} - $change_at; # correcting for position handled in change_at + my $q_end = $v_h->{POS} + length $ref_right; + if($call_type eq 'D') { + $q_end += length $v_h->{REF}; + } + + print $fh sprintf ">REF\n%s%s%s\n", $ref_left, $ref, $ref_right or die "Failed to write REF to blat ref temp file"; + print $fh sprintf ">ALT\n%s%s%s\n", $ref_left, $alt, $ref_right or die "Failed to write ALT to blat ref temp file"; + + my $seq_left = substr($ref_left, -1); + + # -1 as includes the base before and after which would be -2 but need to correct for coord maths + # (for Del and Ins, unsure about DI at the moment) + my $seq_right; + if($call_type ne 'I') { + $seq_right = substr($ref_right, 0, ($r_end - $r_start) - $v_h->{LEN}); + } + else { + $seq_right = substr($ref_right, 0, ($r_end - $r_start)); + } + + + my $change_ref = $seq_left.$ref.$seq_right; + my $change_alt = $seq_left.$alt.$seq_right; + +# printf ">REF\n%s%s%s\n", $ref_left, $ref, $ref_right; +# printf ">ALT\n%s%s%s\n", $ref_left, $alt, $ref_right; +# printf "%s - %s - %s\n", $seq_left, $ref, $seq_right; +# printf "%s - %s - %s\n", $seq_left, $alt, $seq_right; +# printf "%d - %d = %d\n", $r_end, $r_start, $r_end - $r_start; +# printf "%s %s %s\n", $q_start, $q_end, $change_at; + + $v_h->{q_start} = $q_start; + $v_h->{q_end} = $q_end; + $v_h->{change_pos} = $change_at; + $v_h->{change_ref} = $change_ref; + $v_h->{change_alt} = $change_alt; + + return 1; +} + diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfCohortConverter.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfCohortConverter.pm index 180378d..7c8af01 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfCohortConverter.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfCohortConverter.pm @@ -1,9 +1,9 @@ package Sanger::CGP::Pindel::OutputGen::VcfCohortConverter; ########## LICENCE ########## -# Copyright (c) 2014-2018 Genome Research Ltd. +# Copyright (c) 2020 Genome Research Ltd. # -# Author: CASM/Cancer IT +# Author: CASM IT # # This file is part of cgpPindel. # @@ -32,12 +32,13 @@ use Sanger::CGP::Pindel; use Sanger::CGP::Vcf::VcfUtil; use Sanger::CGP::Vcf::VcfProcessLog; use Const::Fast qw(const); +use Sanger::CGP::Pindel::InputGen; use Vcf; const my $SEP => "\t"; const my $NL => "\n"; -const my $READS_AND_BLAT => q{bash -c 'set -o pipefail ; samtools view -uF 3840 %s %s:%d-%d | samtools fasta - > %s && blat -t=dna -q=dna -noTrimA -minIdentity=95 -noHead -out=pslx -out=pslx %s %s /dev/stdout'}; +const my $READS_AND_BLAT => q{bash -c 'set -o pipefail ; samtools view -uF 3840 %s %s:%d-%d | samtools fasta - > %s && blat -t=dna -q=dna -noTrimA -minIdentity=95 -noHead -out=psl %s %s %s && pslPretty -axt %s %s %s /dev/stdout'}; # returns +ve and then -ve results const my $SAM_DEPTH_PN => q{bash -c "set -o pipefail ; samtools view -uF 3844 %s %s:%d-%d | pee 'samtools view -c -F 16 -' 'samtools view -c -f 16 -'"}; @@ -84,6 +85,15 @@ sub init{ $self->{_bas_set} = $args{-bas_set}; $self->_max_inserts() if(defined $self->{_bas_set}); $self->{_all} = $args{-all}; + if(defined $args{-badloci}) { + $self->{_tabix} = Sanger::CGP::Pindel::InputGen::tabix_to_interval_tree($args{-badloci}); + } +} + +sub _interval_hit { + my ($self, $chr, $start, $stop) = @_; + return 0 unless (exists $self->{_tabix}->{$chr}); + return scalar @{$self->{_tabix}->{$chr}->fetch($start, $stop)}; } sub _max_inserts { @@ -141,13 +151,15 @@ sub gen_header{ my @format = ( {key => 'FORMAT', ID => 'GT', Number => 1, Type => 'String', Description => 'Genotype'}, + {key => 'FORMAT', ID => 'S1', Number => 1, Type => 'Integer', Description => 'Pindel S1 score'}, + {key => 'FORMAT', ID => 'S2', Number => 1, Type => 'Float', Description => 'Pindel S2 score, not present for all types'}, {key => 'FORMAT', ID => 'PP', Number => 1, Type => 'Integer', Description => 'Pindel calls on the positive strand'}, {key => 'FORMAT', ID => 'NP', Number => 1, Type => 'Integer', Description => 'Pindel calls on the negative strand'}, - {key => 'FORMAT', ID => 'WTP', Number => 1, Type => 'Integer', Description => '+ve strand reads BLATed (or count for large deletions) to reference at this location'}, - {key => 'FORMAT', ID => 'WTN', Number => 1, Type => 'Integer', Description => '-ve strand reads BLATed (or count for large deletions) to reference at this location'}, - {key => 'FORMAT', ID => 'MTP', Number => 1, Type => 'Integer', Description => '+ve strand reads BLATed to alternate sequence at this location'}, - {key => 'FORMAT', ID => 'MTN', Number => 1, Type => 'Integer', Description => '-ve strand reads BLATed to alternate sequence at this location'}, - {key => 'FORMAT', ID => 'VAF', Number => 1, Type => 'Float', Description => 'Variant allele fraction using reads that unabiguously map to ref or alt seq (to 3 d.p.)'}, + #{key => 'FORMAT', ID => 'WTP', Number => 1, Type => 'Integer', Description => '+ve strand reads BLATed (or count for large deletions) to reference at this location'}, + #{key => 'FORMAT', ID => 'WTN', Number => 1, Type => 'Integer', Description => '-ve strand reads BLATed (or count for large deletions) to reference at this location'}, + #{key => 'FORMAT', ID => 'MTP', Number => 1, Type => 'Integer', Description => '+ve strand reads BLATed to alternate sequence at this location'}, + #{key => 'FORMAT', ID => 'MTN', Number => 1, Type => 'Integer', Description => '-ve strand reads BLATed to alternate sequence at this location'}, + #{key => 'FORMAT', ID => 'VAF', Number => 1, Type => 'Float', Description => 'Variant allele fraction using reads that unabiguously map to ref or alt seq (to 3 d.p.)'}, ); my @blank_fmt = (q{.}) x (scalar @format -1); @@ -191,19 +203,24 @@ sub gen_header{ sub gen_record{ my($self, $record) = @_; + my $ret = q{}; # CHR POS ID REF ALT QUAL FILTER INFO FORMAT GENO GENO my $start = $record->start(); $start-- if(substr($record->type(),0,1) eq 'D'); - my $ret = $record->chro().$SEP; - $ret .= $start.$SEP; - $ret .= $record->id().$SEP; - my $ref = uc ($record->lub . $record->ref_seq); my $alt = uc ($record->lub . $record->alt_seq); + if($self->_interval_hit($record->chro, $start, $start + length $ref)) { + return $ret; + } + + $ret .= $record->chro().$SEP; + $ret .= $start.$SEP; + $ret .= $record->id().$SEP; + $ret .= $ref.$SEP; $ret .= $alt.$SEP; $ret .= $record->sum_ms().$SEP; @@ -215,52 +232,54 @@ sub gen_record{ $ret .= 'RS='.$record->range_start().';'; $ret .= 'RE='.$record->range_end().';'; $ret .= 'LEN='.$record->length().';'; - $ret .= 'S1='.$record->s1().';'; - $ret .= 'S2='.$record->s2().';' if(defined $record->s2()); ## not presant in older versions of pindel $ret .= 'REP='.$record->repeats().$SEP; - # now attempt the blat stuff - my ($fh_target, $file_target) = tempfile( - DIR => '/dev/shm', - SUFFIX => '.fa', - UNLINK => 1 - ); - - my ($q_start, $q_end, $change_pos, $change_ref, $change_alt) = $self->blat_ref_alt($fh_target, $record); - - my $change_pos_low = $change_pos; - $change_pos_low-- if($record->type eq 'D'); - my $range_l = ($record->range_end - $record->range_start) + 1; - my $change_pos_high = $change_pos_low + $range_l; # REF based range, adjusted in func - my $change_l = $record->end - $record->start + 1; - $self->{change_pos_low} = $change_pos_low; - $self->{change_pos_high} = $change_pos_high; - $self->{change_l} = $change_l; - $self->{change_ref} = $change_ref; - $self->{change_alt} = $change_alt; - $self->{q_start} = $q_start; - $self->{q_end} = $q_end; - $self->{file_target} = $file_target; - $self->{type} = $record->type; - $self->{chr} = $record->chro; + # # now attempt the blat stuff + # my ($fh_target, $file_target) = tempfile( + # DIR => '/dev/shm', + # SUFFIX => '.fa', + # UNLINK => 1 + # ); + + # my ($q_start, $q_end, $change_pos, $change_ref, $change_alt) = $self->blat_ref_alt($fh_target, $record); + + # my $change_pos_low = $change_pos; + # $change_pos_low++ if($record->type eq 'I'); + # my $range_l = ($record->range_end - $record->range_start) + 1; + # my $change_pos_high = $change_pos_low + $range_l; # REF based range, adjusted in func + # my $change_l = $record->end - $record->start + 1; + # $self->{change_pos_low} = $change_pos_low; + # $self->{change_pos_high} = $change_pos_high; + # $self->{change_l} = $change_l; + # $self->{change_ref} = $change_ref; + # $self->{change_alt} = $change_alt; + # $self->{q_start} = $q_start; + # $self->{q_end} = $q_end; + # $self->{file_target} = $file_target; + # $self->{type} = $record->type; + # $self->{chr} = $record->chro; # FORMAT $ret .= $self->{_format}; for my $samp(@{$self->{_srt_samples}}) { - my ($wtp, $wtn, $mtp, $mtn) = $self->blat_reads($samp, $record); + #my ($wtp, $wtn, $mtp, $mtn) = $self->blat_reads($samp, $record); $ret .= $SEP; if($self->gen_all || exists $record->reads->{$samp}) { $ret .= './.:'; + $ret .= $record->s1.q{:}; + $ret .= $record->s2 || '.'; + $ret .= q{:}; $ret .= $record->get_read_counts($samp, '+').q{:}; - $ret .= $record->get_read_counts($samp, '-').q{:}; - $ret .= $wtp.q{:}; - $ret .= $wtn.q{:}; - $ret .= $mtp.q{:}; - $ret .= $mtn.q{:}; - my $mtr = $mtp+$mtn; - my $depth = $wtp+$wtn+$mtr; - $ret .= $depth ? sprintf("%.3f", $mtr/$depth) : 0; + $ret .= $record->get_read_counts($samp, '-'); + #.q{:}; + #$ret .= $wtp.q{:}; + #$ret .= $wtn.q{:}; + #$ret .= $mtp.q{:}; + #$ret .= $mtn.q{:}; + #my $mtr = $mtp+$mtn; + #my $depth = $wtp+$wtn+$mtr; + #$ret .= $depth ? sprintf("%.3f", $mtr/$depth) : 0; } else { $ret .= $self->{_noread_gt}; @@ -289,7 +308,7 @@ sub ins_by_sample { return $self->{_ins_set}->{$sample}; } -sub blat_reads { +sub TO_REMOVE_blat_reads { my ($self, $sample, $record) = @_; my ($fh_query, $file_query) = tempfile( DIR => '/dev/shm', @@ -297,8 +316,16 @@ sub blat_reads { UNLINK => 1 ); close $fh_query or die "Failed to close $file_query (query reads)"; + my ($fh_psl, $file_psl) = tempfile( + DIR => '/dev/shm', + SUFFIX => '.psl', + UNLINK => 1 + ); + close $fh_psl or die "Failed to close $file_psl (psl output)"; + + my $read_buffer = $self->ins_by_sample($sample); - my $c_blat = sprintf $READS_AND_BLAT, $self->hts_file_by_sample($sample), $self->{chr}, $self->{q_start}-$read_buffer, $self->{q_end}+$read_buffer, $file_query, $self->{file_target}, $file_query; + my $c_blat = sprintf $READS_AND_BLAT, $self->hts_file_by_sample($sample), $self->{chr}, $self->{q_start}-$read_buffer, $self->{q_end}+$read_buffer, $file_query, $self->{file_target}, $file_query, $file_psl, $file_psl, $self->{file_target}, $file_query; my ($c_out, $c_err, $c_exit) = capture { system($c_blat); }; if($c_exit) { warn "An error occurred while executing $c_blat\n"; @@ -313,162 +340,104 @@ sub blat_reads { # system("cat $file_query"); # ; # print "\n$c_out\n"; +# exit 1; - my ($wtp, $wtn, $mtp, $mtn) = $self->blat_counts(\$c_out, $sample); + #my ($wtp, $wtn, $mtp, $mtn) = $self->blat_counts(\$c_out, $sample); + my ($wtp, $wtn, $mtp, $mtn) = $self->psl_axt_parser(\$c_out, $sample); if($wtp + $wtn == 0) { ($wtp, $wtn) = $self->sam_depth($sample, $record); } return ($wtp, $wtn, $mtp, $mtn); } -sub blat_counts { - my ($self, $blat) = @_; - $self->{read_map} = {}; - my @lines = split /\n/, ${$blat}; - for my $l(@lines) { -#next unless($l =~ m/HX1_20016:2:1214:9678:44450/); -#print $l."\n"; - $self->parse_event(\$l); +sub TO_REMOVE_psl_axt_parser { + my ($self, $blat_axt) = @_; + # collate the data by readname and order by score + my @lines = split /\n/, ${$blat_axt}; + my $line_c = @lines; + # group all reads and order by score + my %reads; + for(my $i = 0; $i<$line_c; $i+=4) { + my ($id, $t_name, $t_start, $t_end, $q_name, $q_start, $q_end, $strand, $score) = split q{ }, $lines[$i]; + push @{$reads{$q_name}{$score}}, [$t_name, $t_start, $t_end, $q_name, $q_start, $q_end, $strand, $score, $lines[$i+1], $lines[$i+2]]; } - my (%wtr, %mtr); - for my $read(sort keys %{$self->{read_map}}) { - my $hits = scalar @{$self->{read_map}->{$read}}; - if($hits > 2) { - print "More than 2 hits!".Dumper($self->{read_map}->{$read}); - exit 1; - } - - # this handles prevents double counting should read 1&2 align across the same positiob - # also allows them to be counted if they go to ALT and REF - my $clean_read = $read; - $clean_read =~ s{/[12]$}{}; - - if($hits != 1 && $self->{read_map}->{$read}->[0]->{target} ne $self->{read_map}->{$read}->[1]->{target}) { - # this can occur due to differences outside the range we are testing, we've already checked the seq is as expected - # when it occurs increment "REF/WTR" - if($self->{read_map}->{$read}->[0]->{strand} ne $self->{read_map}->{$read}->[1]->{strand}) { - if(scalar keys %{$wtr{'+'}} <= scalar keys %{$wtr{'-'}}) { - $wtr{'+'}{$clean_read} = 1; - next; - } - $wtr{'-'}{$clean_read} = 1; - next; + my %TYPE_STRAND; + # sort keys for consistency + READ: for my $read(sort keys %reads) { + # get the alignment with the highest score + for my $score(sort {$b<=>$a} keys %{$reads{$read}}) { + my @records = @{$reads{$read}{$score}}; + next READ if(@records != 1); # if best score has more than one alignment it is irrelevant + my $record = $records[0]; + if($self->parse_axt_event($record) == 1) { + $TYPE_STRAND{$record->[0].$record->[6]} += 1; } - $wtr{$self->{read_map}->{$read}->[0]->{strand}}{$clean_read} = 1; - next; + next READ; # remaining items are worse alignments } - - if($self->{read_map}->{$read}->[0]->{target} eq 'REF') { - $wtr{$self->{read_map}->{$read}->[0]->{strand}}{$clean_read} = 1; - next; - } - $mtr{$self->{read_map}->{$read}->[0]->{strand}}{$clean_read} = 1; - } - return (scalar keys %{$wtr{'+'}}, scalar keys %{$wtr{'-'}}, scalar keys %{$mtr{'+'}}, scalar keys %{$mtr{'-'}}); -} - -sub sam_depth { - my ($self, $sample, $record) = @_; - my $mid_point = int ($record->range_start + (($record->range_end - $record->range_start)*0.5)); - my $c_samcount = sprintf $SAM_DEPTH_PN, $self->hts_file_by_sample($sample), $record->chro, $mid_point, $mid_point; - my ($c_out, $c_err, $c_exit) = capture { system($c_samcount); }; - if($c_exit) { - warn "An error occurred while executing $c_samcount\n"; - warn "\tERROR$c_err\n"; - exit $c_exit; } - return (split /\n/, $c_out); -} - -sub _dump_rec_detail { - my $self = shift; - for my $k(sort qw(change_pos_low change_pos_high change_l change_ref change_alt q_start q_end type chr)) { - printf "%s: %s\n", $k, ref $self->{$k} ? Dumper($self->{$k}) : $self->{$k}; - } - print "\n"; - return 0; + my $wtp = $TYPE_STRAND{'REF+'} || 0; + my $wtn = $TYPE_STRAND{'REF-'} || 0; + my $mtp = $TYPE_STRAND{'ALT+'} || 0; + my $mtn = $TYPE_STRAND{'ALT-'} || 0; + return ($wtp, $wtn, $mtp, $mtn); } -sub parse_event { +sub TO_REMOVE_parse_axt_event { my ($self, $rec) = @_; - - my @rec_d = split /\t/, ${$rec}; - if(exists $self->{read_map}->{$rec_d[$Q_NAME]}) { - my @recs = @{$self->{read_map}->{$rec_d[$Q_NAME]}}; - for my $r(@recs) { - if($r->{'target'} eq $rec_d[$T_NAME] && $r->{'strand'} eq $rec_d[$STRAND]) { - return 0; - } - } - } - # clean up trailing commas - $rec_d[$Q_SEQ] =~ s/,$//; - $rec_d[$T_SEQ] =~ s/,$//; - $self->{rec_d} = \@rec_d; + my ($t_name, $t_start, $t_end, $q_name, $q_start, $q_end, $strand, $score, $t_seq, $q_seq) = @{$rec}; # specific to deletion class my $change_seq = $self->{change_ref}; my $change_pos_high = $self->{change_pos_high}; - if($rec_d[$T_NAME] eq 'ALT') { + if($t_name eq 'ALT') { $change_pos_high -= $self->{change_l}; $change_seq = $self->{change_alt}; } $self->{change_seq} = $change_seq; -#my %lookup = map { $_ => 1 } (qw(HX1_20016:2:1101:2402:12683/2 HX1_20016:2:1102:20983:27363/2 HX1_20016:2:1106:7679:42745/1 HX1_20016:2:1110:29944:60290/1 HX1_20016:2:1120:29589:3348/2 HX1_20016:2:1210:9100:28681/2 HX1_20016:2:1218:22049:68271/1 HX1_20016:2:2122:17208:35977/2 HX1_20016:2:2205:16234:70574/2 HX1_20016:2:2210:28879:48177/2 HX1_20016:2:2216:17888:66056/2)); -#if(exists $lookup{$self->{rec_d}->[$Q_NAME]}) { -# print "$self->{rec_d}->[$Q_NAME]\n"; -#} -#else { return 0;} - # all the reads that don't span the range - return 0 unless($rec_d[$T_START] <= $self->{change_pos_low} && $rec_d[$T_END] > $change_pos_high); - - my $result = {target => $rec_d[$T_NAME], strand => $rec_d[$STRAND], rec => $rec}; - if($self->gap_ok($change_pos_high)) { - push @{$self->{read_map}->{$rec_d[$Q_NAME]}}, $result; + return 0 unless($t_start <= $self->{change_pos_low} && $t_end > $change_pos_high); + # look for the change (or absence) where we expect it + my $exp_pos = $self->{change_pos_low} - $t_start; + if(index($t_seq, $change_seq, $exp_pos) == $exp_pos) { +# print join "\t", $t_name, $t_start, $t_end, $q_name, $q_start, $q_end, $strand, $score; +# printf "\n%s\n%s\n", $t_seq, $q_seq; +# print substr($t_seq, ($self->{change_pos_low} - $t_start), $change_pos_high)."\n"; +# print "$change_seq\n"; +# print "EXP: $exp_pos\n"; +# print "INDEX: ".index($t_seq, $change_seq, $exp_pos)."\n"; return 1; } + +#; +# return 2; return 0; } -sub gap_ok { - my ($self, $change_pos_high) = @_; - my @b_sizes = split q{,}, $self->{rec_d}->[$BLOCK_SIZES]; - my @t_starts = split q{,}, $self->{rec_d}->[$T_STARTS]; - my $iter = @b_sizes - 1; - for my $i(0..$iter) { - if($t_starts[$i] <= $self->{change_pos_low} && $t_starts[$i] + $b_sizes[$i] > $change_pos_high && $self->match_ok($t_starts[$i])) { - return 1; - } +sub TO_REMOVE_sam_depth { + my ($self, $sample, $record) = @_; + my $mid_point = int ($record->range_start + (($record->range_end - $record->range_start)*0.5)); + my $c_samcount = sprintf $SAM_DEPTH_PN, $self->hts_file_by_sample($sample), $record->chro, $mid_point, $mid_point; + my ($c_out, $c_err, $c_exit) = capture { system($c_samcount); }; + if($c_exit) { + warn "An error occurred while executing $c_samcount\n"; + warn "\tERROR$c_err\n"; + exit $c_exit; } - return 0; + return (split /\n/, $c_out); } -sub match_ok { - my ($self, $t_start) = @_; - # check the expected seq is found at the index of the low boudary - my $exp_index = $self->{change_pos_low} - $t_start; - return 0 if($exp_index < 0); - - return 1 if(index($self->{rec_d}->[$Q_SEQ], $self->{change_seq}, $exp_index) == $exp_index); - return 0 if(index($self->{change_seq}, q{,}) >= 0); # don't allow gaps in the target for remaining checks - # allow for a minimal number of mismatches - my $change_len = length $self->{change_seq}; - my $q_exp = substr($self->{rec_d}->[$Q_SEQ], $exp_index, $change_len); -#print "cs: $self->{change_seq}\n"; -#print "qe: $q_exp\n"; - - return 0 if( substr($self->{change_seq}, 0, 1) ne substr($q_exp, 0, 1) || substr( $self->{change_seq}, -1, 1) ne substr($q_exp, -1, 1)); - return 0 if(index($q_exp, q{,}) >= 0); # don't allow gaps in the query for remaining checks - return 0 if(length($q_exp) < $change_len); - my $mismatch = ($q_exp ^ $self->{change_seq}) =~ tr/\0//c; - return 1 if(($mismatch / $change_len) < 0.2); +sub _dump_rec_detail { + my $self = shift; + for my $k(sort qw(change_pos_low change_pos_high change_l change_ref change_alt q_start q_end type chr)) { + printf "%s: %s\n", $k, ref $self->{$k} ? Dumper($self->{$k}) : $self->{$k}; + } + print "\n"; return 0; } -sub blat_ref_alt { +sub TO_REMOVE_blat_ref_alt { my ($self, $fh, $record) = @_; my $ref_left = $record->ref_left; my $ref_right = $record->ref_right; @@ -508,8 +477,8 @@ sub blat_ref_alt { } - my $change_ref = lc ($seq_left.$ref.$seq_right); - my $change_alt = lc ($seq_left.$alt.$seq_right); + my $change_ref = uc ($seq_left.$ref.$seq_right); + my $change_alt = uc ($seq_left.$alt.$seq_right); # printf "%s\n", $record->ref_left; # printf "%s\n", $record->ref_seq; diff --git a/perl/t/1_pm_compile.t b/perl/t/1_pm_compile.t index 497df8a..9bd406e 100644 --- a/perl/t/1_pm_compile.t +++ b/perl/t/1_pm_compile.t @@ -1,21 +1,21 @@ ########## LICENCE ########## -# Copyright (c) 2014-2018 Genome Research Ltd. -# +# Copyright (c) 2014-2018 Genome Research Ltd. +# # Author: CASM/Cancer IT -# +# # This file is part of cgpPindel. -# -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 Affero General Public License for more -# details. -# -# You should have received a copy of the GNU Affero General Public License +# +# cgpPindel is free software: you can redistribute it and/or modify it under +# the terms of the GNU Affero 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 Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . ########## LICENCE ########## @@ -38,7 +38,7 @@ my $lib_path = "$Bin/../lib"; # Add modules here that cannot be instantiated (should be extended and have no 'new') # or need a set of inputs - these should be tested in own test script -use constant MODULE_SKIP => qw(Sanger::CGP::Pindel::InputGen::Pair Sanger::CGP::Pindel::InputGen::Read Sanger::CGP::Pindel::OutputGen::CombinedRecordGenerator Sanger::CGP::Pindel::OutputGen::PindelRecordParser Sanger::CGP::Pindel::OutputGen::VcfConverter); +use constant MODULE_SKIP => qw(Sanger::CGP::Pindel::InputGen::Pair Sanger::CGP::Pindel::InputGen::Read Sanger::CGP::Pindel::OutputGen::CombinedRecordGenerator Sanger::CGP::Pindel::OutputGen::PindelRecordParser Sanger::CGP::Pindel::OutputGen::VcfConverter Sanger::CGP::Pindel::OutputGen::VcfBlatAugment); my $init_cwd = getcwd; diff --git a/perl/t/data/blat/D.vcf b/perl/t/data/blat/D.vcf new file mode 100644 index 0000000..34c489f --- /dev/null +++ b/perl/t/data/blat/D.vcf @@ -0,0 +1,3385 @@ +##fileformat=VCFv4.1 +##fileDate=10200428 +##source_20200428.1=pindelCohort_to_vcf.pl_v3.3.0 +##reference=/lustre/scratch119/casm/team78pipelines/reference/human/GRCh38_full_analysis_set_plus_decoy_hla/genome.fa +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##FORMAT= +##FORMAT= +##FORMAT= +##vcfProcessLog_20200428.1=,InputVCFVer=> +##vcfProcessLog_20200428.2=,InputVCFVer=<3.3.0>> +##SAMPLE= +#CHROM POS ID REF ALT QUAL FILTER INFO FORMAT PD26988a +chr10 11201 CA C 390 . PC=D;RS=11201;RE=11205;LEN=1;S1=11;S2=849.236;REP=3 GT:PP:NP ./.:10:0 diff --git a/perl/t/data/blat/DI.vcf b/perl/t/data/blat/DI.vcf new file mode 100644 index 0000000..fac9b07 --- /dev/null +++ b/perl/t/data/blat/DI.vcf @@ -0,0 +1,3385 @@ +##fileformat=VCFv4.1 +##fileDate=10200428 +##source_20200428.1=pindelCohort_to_vcf.pl_v3.3.0 +##reference=/lustre/scratch119/casm/team78pipelines/reference/human/GRCh38_full_analysis_set_plus_decoy_hla/genome.fa +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##FORMAT= +##FORMAT= +##FORMAT= +##vcfProcessLog_20200428.1=,InputVCFVer=> +##vcfProcessLog_20200428.2=,InputVCFVer=<3.3.0>> +##SAMPLE= +#CHROM POS ID REF ALT QUAL FILTER INFO FORMAT PD26988a +chr10 22777 AGAAACTGTG ACTGTGAGATAGATATATATAGATAGATATAT 105 . PC=DI;RS=22777;RE=22787;LEN=9;S1=6;REP=0 GT:PP:NP ./.:0:5 diff --git a/perl/t/data/blat/SI.vcf b/perl/t/data/blat/SI.vcf new file mode 100644 index 0000000..f83e652 --- /dev/null +++ b/perl/t/data/blat/SI.vcf @@ -0,0 +1,3385 @@ +##fileformat=VCFv4.1 +##fileDate=10200428 +##source_20200428.1=pindelCohort_to_vcf.pl_v3.3.0 +##reference=/lustre/scratch119/casm/team78pipelines/reference/human/GRCh38_full_analysis_set_plus_decoy_hla/genome.fa +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##contig= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##FORMAT= +##FORMAT= +##FORMAT= +##vcfProcessLog_20200428.1=,InputVCFVer=> +##vcfProcessLog_20200428.2=,InputVCFVer=<3.3.0>> +##SAMPLE= +#CHROM POS ID REF ALT QUAL FILTER INFO FORMAT PD26988a +chr10 11643 C CG 150 . PC=I;RS=11643;RE=11649;LEN=1;S1=6;S2=421.908;REP=4 GT:PP:NP ./.:0:5 diff --git a/perl/t/data/blat/chr10_1-23700.fa b/perl/t/data/blat/chr10_1-23700.fa new file mode 100644 index 0000000..0f3edae --- /dev/null +++ b/perl/t/data/blat/chr10_1-23700.fa @@ -0,0 +1,396 @@ +>chr10 +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNCTAACCCTAACCCTAACCCT +AACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCT +AACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCT +AACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCT +AACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCT +AACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCT +AACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTTAACCC +TAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTAACCCTCACCCTTCTAA +CTGGACTCTGACCCTGATTGTTGAGGGCTGCAAAGAGGAAGAATTTTATTTACCGTCGCT +GTGGCCCCGAGTTGTCCCAAAGCGAGGTAATGCCCGCAAGGTCTGTGCTGATCAGGACGC +AGCTCTGCCTTCGGGGTGCCCCTGGACTGCCCGCCCGCCCGGGTCTGTGCTGAGGAGAAC +GCTGCTCCGCCTCCGCGGTACTCCGGACATATGTGCAGAGAAGAACGCAGCTGCGCCCTC +GCCATGCTCTGCGAGTCTCTGCTGATGAGAACACAGCTTCACTTTCGCAAAGGCGCAGCG +CCGGCGCAGGCGCGGAGGGGCGCGCAGCGCCGGCGCAGGCGCGGAGGGGCGCGCCCGAAC +CCGAACCCTAATGCCGTCATAAGAGCCCTAGGGAGACCTTAGGGAACAAGCATTAAACTG +ACACTCGATTCTGTAGCCGGCTCTGCCAAGAGACATGGCGTTGCGGTGATATGAGGGCAG +GGGTCATGGAAGAAAGCCTTCTGGTTTTAGACCCACAGGAAGATCTGTGACGCGCTCTTG +GGTAGAGCACACGTTGCTGGGCGTGCGCTTGAAAAGAGCCTAAGAAGAGGGGGCGTCTGG +AAGGAACCGCAACGCCAAGGGAGGGTGTCCAGCCTTCCCGCTTCAACACCTGGACACATT +CTGGAAAGTTTCCTAAGAAAGCCAGAAAAATAATTTAAAAAAAAATCCAGAGGCCAGACG +GGCTAATGGGGCTTTACTGCGACTATCTGGCTTAATCCTCCAAACAACCTTGCCATACCA +GCCCATCAGTCCTCTGAGACAGGTGAAGAACCTGAGGTCGCAGGAGGACACCCAGAAGGT +CCAGAGAGAGCCTCCTAGGCCCCCCACCTCCCCCCGTGGCAGCTCCAACCCCAGCTTTTT +CACTAGTAAGGCAGTCGGGCCCCTGGGCCACGCCCACTCCCCCAAGCGGGGAAGGAGCTT +CGCGCTGCCGCTTGGCTGGGGACTGGGCACCGCCCTCCCGCGGCTCCTGAGCCGGCTGCC +ACCAGGGGGCGCGCCAGCGGTGTCCGGGAGCCTAGCGGCGCGTGTGCAGCGGCCAGTGCA +CCTGCTCTGGCCCTCGCCGCGGTCTCTGCCAGGACCCCGACGCCCAGCCTGACCCTGCCA +TTCAGCGGGGCTGCGGCTCCACGGCCTGCGACAGCAGCCCCACCTGGCATTCAGCGCGCT +CCCGGGGGCAGAGGTCGCGGTGTCCTCACGCTGTGGTGCCGGCCTACAACCCCCACGCCG +GGCTCGGGCCCGGCGGAGGAGGGCGATGCTCCCCGGGTAGGACAAACCGGTCACCTGGGC +TGCGACGGCGGCTTAGGGGCAGAAGCGGCGGTCCAGGGCCGCCTGGCGCAGCAGCCTGTC +CCAGCCGCGGTCCCTGCAGTCCCTCCCTGGCGGCTGCGCAGCCGTCCCACGACAGGGGCC +ATAAACTCTCCAGAGCGGAAAGCCGCACCCTGGTGGCCCGGCCCCGCGCCCAGACCTGGC +GGCCGCTGGCACCTGACCCGCTGCATGGGTCTCCAGGGAGCTCGCTGCCCACCCGGCGCT +GCAGGCTCGGCTCCCTCGTACACTCTCTGGTAGGTGCTAGGGACGACCCTATGGGCCAGC +TTGCCATGCCCAGTCCCCAGGCCGCACCCACCCTGGCTCCCTGGGCTAGGGGACTGGCTC +CTCCTGTGAGTCGTGGGTCTGGGAGGCAGGGGCGTTAGGGGAGAGTGAGGGACCGAGGGC +AGCCCCTGCTGTGTGCACAGCGAGGTCGTGCACAGGCGTCTGTTGCAGAGCGTGCAGCTT +CAGATGAGACTGGATTGCAGGTGGAGATGACTGTGGGTGCGCACACCTGGAGGTGAAGGG +GAGGCAGCCTGTCTACCTGACCCATGAAATACAGGAGACTGTACCCCAGAAGCAGCGGGT +TCACTGCTCCATTGATTAAGCAAGTCTGGGACACACATGTAGCTAAGCTGTGAGTTCTGT +ACCAGCGATCCCAACACCCACGCCCTCAGAAAGACACTGGTGTGGGGCCTGGGTGCTTGT +CAGGCCTGAAAGTGGAGAGCACGGGCCAGAGACACTGAGTAGGGGGAACCCACCCTAGGG +CTCTGAGGGACGACGATGTGGGGAGCTGGTGACAGAGCCTGAGCTGGCCCAATGTTGCAC +GGTGGGGACAGATTCGAGGTACAGTGGGGACTGGTGACCTCAGTTCCCAGTGTCCCAGCC +TGGCCTCCCAGTCCACCCAGCAATTAGTGGGTGCTGCCCTGCAAAGACTCTGGGGGTGCC +TCAGCCCTCCTCATCACACGTGACTGGTGACTTCTGTGTCCACCCGCACAATAAGAGGGA +TCTTCTCTCACTTTCAGGCAAGCCCAAGAAAGTCAGGGGCCTATGTGAGCCAAAGAGGAG +AGAAGGTGATGCCTCAGCCCAGTGTTTCTGCCCCACCTCGCTTGTGGCCTTCGGAACTTG +ATTTGCACCGCAGGAAAATGGGCAATGAAAACCCCTCCCTAACTGGCTTCTCAGTCCACT +CTGACCAGCCCACTGCACAGCGCCCACCCTGCAGCTCCAGGTACAGAGGCTGGGATGGCT +CTGGGCTGACCTAAGGGCCTTCTGATGGCTCCAACCCTCGGGATGCCTCATGCTCACCCT +TTGGCACCCACCTGACAGCTCAGCATCTCTGCTCTCTGCCATCCTCAATGCCTGCTCTAG +ACAAGCCCAAGTCCCCCAGGAGTGGCAGAGGGAACTGAGCCGAAAACTAAGTCTCGGCTC +ACTGAACCCCAAGTGGGCTGTCCAGCCTCGCCCTTCAGTTCACAACCCCAGGCAGGTTCC +CTCCAGGGATGTGATCCCAGGGGCCACAGCAGCACATTCTGGCCTAACCTATCCACTATT +TAAACAGTTACTGAAAAGGCCAGGATGGCCGTGGGCCCTGACATTAATCCCCTTTCTCTG +TGAGGGGGCTGGGTTGGGTTTGCCATCCTGATGTCTTTGTGGAAAGAGCTGGCAGGTGAA +GCAAGTCTCAGGGGCCAGCCATGGGACAAGGAACCTAGGACTGGCCTCTGCTGGAACCCT +CTGAGGCCCCTGCGGACAGGAGGATCCAATGGAGGTCTAGCCACCCCTCCCAGGTTGGTG +CTCACAGCCCCTCCCTGGCCCACTCCCTGCACACCTGCACCTGCTGGTCTCTGGGAGAGG +AGCATCCATCCATCTTGTGCGCATAGCTTTCGGCTCCATTTTCATGAGGATGGTCTCCTT +GGCAGAAATGCCCATTAGGGGATCCTGAGCCTGTGCTAGCTCTTCTCTAAGTGCCAAAGC +CAGTGAGAGGGACTTGAAAACTCAAGACTTATTAACAGTATTTTCTGCATTTTGTGCTTT +CAGGGTTGTTTTTTCCTTAAAATGTGTAAAAACAAACATTGAGATTTCTATCTTTTATAT +AATTTGGATTCTGTTATCACACGGACTTTTCCTGAAATTTATTTTTATGTATGTATATCA +AACATTGAATTTCTGTTTTCTTCTTTACTGGAATTGTTAACTGTTTTATAGGCCAAATCT +TTTAAAAAAAACACATCTCTCTAATTTCTCTAAACATTTCTAATTACATATATATTTACT +ATACCTAATACACTACTTTGGAATTCCTTGAGGCCTAAATGCATCGGGGTGCTCTGGTTT +TGTTGTTGTTATTTCTGAATGACATTTACTTTGGTGCTCTTTATTTTGCGTATTTAAAAC +TATTAGATCGTGTGATTATATTTGACAGGTCTTAATTGACGCGCTGTTCAGCCCTTTGAG +TTCGGTTGAGTTTTGGGTTGGAGAATTTTCTTCCACAAGGGATTGTCTTGGATTTTTCTG +TTTCTCCCTCAATATCCACCTGGAAAACATTTCAATTAATTTATATTTACTTAAATATTT +CTGTGCAAAAACTGTGTACAAAAGCCCCAAAGCATAATTTGTGCAGTTGAGCGCATGTTC +TGTTGTTCAGCATTTATGGTGGTTGGTAGTGGAAAAGATTTTTAGAATATGTGGATTTTC +GGGATATTCCCAGAAGCCCAGATAGCGACACTTTACCTTTGGAGGAATTACTTCTCAGAA +TATTGCACACAATCAATCGCCTTTGGAAGGAGCATATATCCCCAGCAAAAGCTCTGGTTT +TTTGAAGTCTGTATTGTGTGTTACTTCCAGGAGAATATGCAATGATGACAATGTTATTAG +ATGATTCAAATATGAAGTGCTGTTATGCCAAACAATGAATCTTTGTGTTATACATTATGC +CTAACTATAAATCTTTGTGTTATACATTTTAATGTCATTGGAGAGTACTCCTGTCTTCTT +GGCATTATTGATAATTAGATTCTAATTGCTAATAAGTCAGAAAAATTAGGAACACCAAAT +TTCAGTTGTCTCAAAAGCACTCCTCTTATTAAATTTGGATGTTTACCTTTATCACATCAA +AAGAAATATTGTTAGAAAGGTGTTTAATGTTTTGCAGATGGATAGATTACTGTTATTAGT +TCTCATTTCATTGTTAATTTTTAAAACCATAAGGTTGGAAGTATCAATATGCCTTTCAAT +ATACCTTAGTGGAATTTATTAAATTTTCATGGATGTCCTTTAGGGGGTTCAGGAAGTTAT +TTCTATTGCTAGATTTCTGGAAGATTTATCAGGAATGAGTGTCAGACATTGTCAGACGTC +CATTGAAATCATCATGGTCTTTTCCTTTATTCTATTAATATGATGTATTACACTGATTGA +TTTTTAAATTTGTATTGGTAGGATAATTCCACTTGGTTATATTGTCTAACTTTTTTCTAA +TTTTCTTTCATTTTTATTACAGATGAGGCCTCACTCTGTCACCCAGGTTGGGGTGGAGTG +GCACAGTCACAGCTCACTATAACCTCAAGCTCCTGGGCTCAAGTGATCCTGCCACCTCAG +CCTCCTAAGTAGCTGGAACTACAGATGTGCACTGCCATGCCAGGCTTGTCTAACATTTTT +ATGTGTTGCTTCATCCAGTTTGCTAGAGTTTTTGGAGATTTCTGTCTTCATTCATGAGGG +ATAATAGTCTGCACTTTTATTTTCTTGTGATACTTTTGTCTGATTTGTTATCTGGGTAAT +ACTGGCCTTGAAAATGAATTGATGTTTTCCTGCTTCTCTGCTTTGCAAGTGTTTGTGAAG +GATTGGTTATTCATTAAGTGTTTAATAGAATTCACTAGTGAAGCTATGTGAGCCAGGGCT +AGACTGATGAAGAGTTTTCATTAGTCTAATCTGTTTACTTGCTGTATAAGTACGCATATA +TTCTCTTTCTTCTTGATTTAATTTTACACTTTGTGTATAGCAGGGAATCTGTGTCTAATT +TGTAGTATTTCATGCTTCTAGGTTTTCATGGCAGTTGAGATGTAAGAATAACAATAATGT +TGGGAGAAGGAAGTTGTGGACAATCCATGAATATCCCAACATCTGTTGTAGGAAGGTTAA +GATTACTTTTTTTTTTTTTGCTGTACTGAACTGAATACTCTTATTTATAATGTCAGACAA +ATGTAATGTTGTATATAAATAGAACTAGGAAAATGTGCCATTTGTCTTAGTATTTAATCA +AGATGGAAGTCTGGGCCTACCTCCTCTCTTTTATTAATATGTAGACAGGACACCAACACA +AATTAGAATGAAGACAAACAAAATGTTAGCAAATGAAGAATGGTATCAATTGGTTAAAAT +GTGATGAAATAGAGTGGTGAATATTTACATAGAATCCATGATGTGTTAGGTGCTATTTCA +AGCTATTTGCACATATAGTTTTAATACCAATGACGTTAAAATGTATAACACAAAGATTCA +TATAAATAAAAATTACAACATTGTAAATAATATTAGGTGACACTAAAACTGTCATAGAAA +TACACATTTATATAAAACATAAAGTAACATGAAGTATTAAATTTTAGAAACTTTGATTAC +TAATCAGATGAACAACTGATTAGCCTTTTTATCCAGTAAAAAAGGCATACATATTATTTT +CAAATTCCAGAGACAAATATTTTAAATATTGAAGTTGAAGACCTAAAAATGTGTCACTGA +CCTCATGGAAGTAGATATTCACTAGGTGATATTTTCTAGGCTCTCTGAAATTATATCAGA +AAAATGTGAATTAGAATATAACCCATAAATAATATCTGGCCACATACAAAGTAATTGAAG +ATCAATTTAAATGGCTATTGGATTAAGAAATAGGGACTGAGGTAAATTTGCAGTGTCAGG +GAGGATCTAAGGAGGAAGCATTGACACTGGAGCCCAAGGACCTGGGATCACAGAACAGAT +TCTACCAGTGCTAACTTACTGCTCCACAGAAAACATCAATTCTGCTCATGCGCAGGTACA +ATTCATCAAGAAAGGAATTACAACTTCAGAAATGTGTTCAAAATATATCCATACTTTGAC +ATATTAATGAAGTAATCACATTCTACACATAACTACTCCATATGGAATACTGGGGAGGAG +GTGTTCCAAATAAAGAGACTGAGGATTTCTCATGAGAACTCAGTGTCTGCTAGAAAATAT +CTAAGTAAAATATTTTACTTATGTGGAAAGTGTGGATGTTTGTGCATCAAAAGTTTCAAG +AATCCCTAAAATTTACAATGGAGATGAGGAGAAAATATCAGAATTTCCCAGCACCAGAAA +TAAGGCAAGAAAAAATTCAGAGGGGTTGTAAATGTGAAAAGCCAATGGCTGGTCACACAG +CAACATTGATAACCTTGTGCCTGGACAACTAGAATAAATACATAAACATACACATTGAAA +ATATTTCCAATATTAGATCTCCCTCATGTGAGAACTAAATTATAAAGATTGAAGCATAGA +AGAAAATAAGCTACCAGAATAAATTTGATTACACATAAATTTCTGATATTGAAACTGTCA +CAAATGTTTAAGTTGGTAGTGGAAGACAAAGGACATATAATCTTGGGAGTCCTAAGGCCC +TGCCCACTGCCAGTCCCTCCACACTACTACAGCTGATGCTTTCTGGAAATCACCACCTCC +TGGCAGGAGCCCAACCAGCACAAATATAGAGCATTAAACCACCAAAGCTAAGGAGGCTCA +CAGAGTCTATTGCACCCTTCACCACCTCCACTGGAACAGGCGCTGGTATCCATGGCTCAG +AGACCCAAAGATGGTTCACATCACAGGGCTCTATGCAGACAACCCCCAGTACCAGCCCAA +AGCCACGTAGACCTGCTGGGTGGCTAGACCCAGAAGAGAGACAACAATCAATGCACTTTG +GCTTACAGGAAGCCATGCCCATAGGAAAAAGGGGAGAGTACTACGTCAAGGGAACACCCC +GTGGGATGAAAGAGTCTGAACAACAGTCTTCAGCCCTAGACCTTTCCTCTGACAGAGTCT +ACCAAAATGAGAAGGAACCAGAAAACCAACCCTGGTAATCTGACAAAACAAGAATCTTCA +ACACCCCCCAAAAAATCACACCAGTTCATCACCAATGGATCCAAACAAAGAAGAAATCAC +TGATTCATCTAAAAAAAAATTCAGGTTAGTTATTAAGCTAATCAGGGAGGGGCCAGAGAA +AGATGAAGCCCAATGCAAGAAAATCCAAAAAATGATACAATACGTGAAGGGAGAATTATT +CAAGGAAATAGATAGCTTAAATAAAAAAATAAAAAATCAGGAAACTTTGGACGTACTTTT +AGAAATGTGAAATGCTCTGGAAAGTCTCAGCAATAGAATTGAACAAGTAGAAGAAAGAAA +TTCAGAATTCGAAGACAAGGTCTTTGATTTAACCCAATCCAATAAAGACAAAGAAAAAAG +AATAAGAAAATATGAGCAAAGTCTCCAAGGAGTCTGGCATTCTGTTAAATGATGAAACCT +AACACTAATTGGTGTACCTGAGGAAGAAGTGAATTCTAAAAGCCAGGAAAACATATTTGG +GAGAATAATCTAGGAAAACTTCCATGGCCTTGTGAGAGACCTAGACATCCAAATACAAGA +ACCACAAATAACACCTGGGAAATTCATCACAAAAAGATCTTAGCCTAGGCACATTGTCAT +TAGGTTATCCAAAGTTAAGACAAAGGAAAGAATCTTAAGAGCTGTGAGACAGAAGCACTA +GGTAACCTATAAAGGAAAACCTGTCAAATTAACAGCAGATTTCACAGCAGGAACCTTACA +AGCTAGATGGGATTGGGGCCCTTTCTTCAGCCTCCTCAAACAAAACAATTATCAGCCAAG +AATTTTGTATCCAGCAAAACTAAACATCATATATGAAGGAAAGATACAGTCATTTTCAGA +CAAACAAATGCTGACAGAATTTGCCATTACCAAGCCAGGACTCTAAGAACTGCTAAAAGG +AGCTCTAAATCATGAAACAAATCCTGGAAACACATCAAAACAGAACTTCATTAACGCATA +AATCACACAGGACCTATAAAACAAAAATACAAGTTAAAAAACAAAAACAAAGTACAGAGG +CAACAAAGAGCATGATGAAAGCAATGGTACCTCACTTTTTAATACTAATGTTGGTTGTAA +ATGGCTTAAATGCTCCACTTACAAGATACAGAACCACAGAATGGATAACAACTCACCAAC +TAACTATCTGCTGCCTTCAGGAGACTCACCTAACACATAACGACTTACATAAACTTAAGG +AAAGTGGTAGAAAAAGGCATTTCATGCAAATGGACACCAAAAGCAAGCAGCAGTAACTAT +TCTCATATGAGACAAAACAAACTTTAAAGCAACAGTAGCTAAAAGAGACAAAGAGAGACA +GTATATCATCTGTCACCTGACAGTCTCATCCAACAGAAAAATATGACAATCCTAAACATA +TGTGAACCTAACACTGGAGCTCCCAAATTTATAAAACAATTACTAGTAGACATAAGAAAT +AAGATAGACAGCAACACAATAATAGTGGGGGACTTCAATACTCCACTGACAGCACTAGAC +AGGTCATCAAGACAGAAAGTCAACAAAGAAACAATGGATTTAAACTATACTTTGGAACAA +ATGGACTTAACAGATATATATAGAACATTTCATCCAACAACCACAGAATACACATTCTAT +TCAACAGCACATGGAATTTTCTCCAAGATAGACCATATGATAGGCCATAAAATGAGTCTC +AATAAATTTAAGAAAATTGAAATTGTATCACGCACTCTCTCACATCACAATGGAATAAAA +CTGAAAATCAACTCCAAAAGGAATCTTCGAAACCATGCAAATACATGGAAATTAAATAAC +CTGCTCCTGAATGAGCATTGGGTGAAAAACGAAATCAAGATGGAAATGTAAAAAATTTCT +TCGAACTGGATGACACAACCTATCAAGACCTCTGGGATACAGCAAAGGCAGTGCTAAGAG +GAAAGTTTATAGCACTAAACACCTACGTCGAAAAGTCTGAAAGAGCACAGACAATCTAAG +TTCACATCTCAGGGAACTAGAGAAGGAGGAACAAGCCAAACCCAATCCCAGCAAACAAAG +GAAATAACCAAGATCAGAGCAGAACTAAATGAAATTGACACAACAACAACAACAACAAAA +ATACAAAACATAAATAAAACAAAAATTTGGTTATTTGAAAAGATAAAATTGATAAACCAT +TAGCAAGATTAACCAAGAAAAGGAGAGAAAATCCAAATAACCTCACTAAGAAATGAAACA +GGGGATATTACAACTGACACCACTGAAATATTAAAGATTATTCAAGGGTACTATGAACAC +CTTTTAGCACATAAACTAGAAAACCTAGAAGAGTTGGATAAATTCCTGGAAAAATACAAC +CCTCCTAGCTTAAATCAGGAAGAATTAGATACCCCAAGCAGACCAATAAAGCAAGCAGCA +AGATTGATAGGGTAATTTAAAAATTACCAACAAAAAAAGCCAAGGACCAGACGGATTCAC +AGCAGAATTCTACCAGACATTCAAAGAAGAAATGATACCAATCCTTTCACACTATTCCAC +AAGACAGAGAAAGAAGAAACCCTTCCAATTCATTCTATGAAGCCAGCATCAGCCTAATAC +CAAAACCATGAAAGGACATAACCAGAAAAGAAAACTACAGACCAATATCCTTAATGAACG +CAGATGCCAAAATCCTTAACAAAATACTATCTAACTGAATCCAACAACATATCAAAAAGA +TAATCCACCAAGATCAAGTGGGTTTCATACCAGTGATACAGGAATGGTTTAACATATGCA +AGTCAATAAATGTGATACACCAAATAAACAGAATTTAAAAAAAACTCACATGATTATATC +AACAGATGCAGAAAAAGCATTCAACAAAATCTAGCATTGCTTTATGATTAAAGCTCTCAG +CAAAATAGGCATACAAGGGACATACCTTAATGTAATAAAAGCCATCTATGACAAACCCAC +AGCCAACATAATACTGAATGGGGAAAAGGTGAAAGCATTCCCTTTGAGAATTGGAACAAG +ACGAGGAGACTACTCTCACCACTCCTCTTCAACATAGTACTGGAAGTCCTAGCCAGAGCA +ATCATACAAAGGAAGGAAATAGAGGAAATCCAAATCGGTAAAGAGGAAGTCAAACTGTCA +CTGCTTGCTGACAATATGATCTTTCACCTTGAAAACCCTACAGACTCCTCTAGAAAGCTC +CTAGAACTGATAAAAGAATTCAGCAAAGTTTCCAGATACAAGATAAATGTACACAAATCA +GTAACTCTTCTATACATCAACAGCTACCAAGCAGAGAATCACATCAAGAACTCAACCCCT +TTTACAATAGCTGCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACTTAGGAATATA +CCTAGCAAAGGAATCAAAAGACCTCTACAATGAAAATTACAAAACACTGCTGAAAGAAAT +CATAGATGGAGCCAAGCATGGTGGCGCATGCCTATAATCCCAGCTACTCGGGAAGCTGAG +GCAGGAGAATCGCTTGAACCCAGGAGGCAGAAGTTGTAGTGAGCCGAGATCACACCATTG +CACTCCCACCTCAGCGACAAGAGCGAAACTCCCTCTGAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAACAAGAAAGAAAAGAAATCATAGATGACACAAACAAATGGAAACGCACCCCCAT +GCTCATGGATGGGTAGAACCAATATTGTGAAAATTACCATTCTGTTAAAGGCAATCTACA +AATTCAATGCAATCCCCATCTGAACACCACCATCATTCTTCACAGAATTACAAAAACGAT +TCTAACATTAATATGGAACCAAAAGAGAGCCATGTAGCCAAACCAAGGCTAAGCAAAAAG +AACAAACCTGGAGGCATCACACTACTTGATTTCAAACTGTACAATAAGGCCACAGTTACC +AAAACAGCATGGTACTGGTTTAAAAATAGGCATATACACCAATGGAACAGAAGAGAGAAC +CCAGAAATTAACCCAAATACTTACAGCCAACTGATCTTCAACAAAGTAAACAAAAACATA +AAGTGGGGAAAGGACACCCTTTTCAACACATGATGTTGGGATAATTGGTGAGCCACATGT +AGGGGAATAAAATTGGATTCTCATCTCTCACCTTATATAAAAATCGACTCAAGATGGATT +AAGAACTTAAACCTAATACCTGAGCTATAAAAATTCTAGAAAATAACACTGGATAAACCC +TTCTAGACATTGGCATAGGCAAGGATTTCATGATGAAGAACCCAAATGCAAATGCAATAA +AAACAAAGATAAATAGTTGGGACTTAATTAAACTAAAGAGCTTTTGCATGGCAAAGGGAA +CAGTCAGCAGAGTAAACAGACAACCCACAGAGTGGGAGAAAATCTTCATAATCTATACAT +CTGACAGAGGACTAATATCCAGAATCCACAACAAACTCAAACAAATCAGTATGAAAGAAA +AAAACAACCCCATCAAAAAATGGGCTAAGGACATGAATGGACAGTTCTCAAAAGAAGATA +TGCAAATGGCCAACAAACATAAATAAAAATGCTTAATATCACTAATGATCAGGGAAATGC +AAATCAAAACCACAATGCAATACCACCTTACCTCCTGCAACCCAAAAATAAAAAAACAGT +AGATGTTGCCGTGGATGCAGTAAACAGGGAACACTTCTACACTGCTGGAGGGAATGTAAA +CTAGTATGCCACTATCAAAAATAATGTGGAGATTCCTTAAATAACTAAAAGTAGAACCAC +CATTTGATCCAGCAATCCCACTACTAGGTATCTACCCAGAGGAAAATAAATCATTATTCA +AAAAAGATACTTGCACACGCAAGTTTATAGCAGTACAATTCACAATTGCAAAATCGTGTA +ACCAACCCAAGTGCCCATCAGTCAACAAGTGGATAAAGAAACTGTGAGATAGATATATAT +AGATAGATATATAGATATATATATATAGATAGATAGATAGATATAGATAAATATATATAG +ATATCTATAGATATATAGATATAGATATATATAATGGAATACTATGTAGCCATAGAAAGG +AATGAATTAACAGCATTTCCTGTGACCTGGACGAGATTGGAGACTATTGTTCTAAGTGAT +GTAACCCAGGAATGGAAAACTCAACATTGTATGTTCTCACTGATATGTAGGAGCTAAGCT +ATGAGGACACAAAGGCATAAGAATGATGCAACGGACTTTGGGGACTTGGGAGGAAGAGTG +GGAGGGAGGCGAGCAATAAAAGACTACGAATATGGTGCAGTCTATACGGCTTGGGTAATG +GGTGCACCATAATCTCACAAATCACCGCTAAGGAACTTACTTATGTAACCAAATACCATC +TGTACCCCGATAACTTATGAAAAAATTAAATAAAAATAATAAATGAAAAATAAAGTATAA +CACACCAAGAAAGTGAAGAGATAACCCACAGAATGGGAAAAAATGATTGCAAACTATCCA +TCTGACAAAGGATTAATAACTGCAATATGTAAGAAGCTCAAACAACTCTACAAGAAAAAA +TCTAATAACCTGGTCAAAAATGGCCAAAAAGATTTGAAGATATATTTCTCAAAAGAAGAC +ATACACATGGCAAACAGGCATATGAAAAGGTGCTCAAAATCATTGATAGAGAAATGCAAA +TCGAAACTGAAGTGTGACATCATCTCACTGGAGTTAAAATGGCTTATATGCAAAATACAG +GAAATAACAAATGCTAGCGAGAATTTGGAGAAAAGGGAACCCTCATTTACTGTTGGTGGG +AATGTAAATTAGTACAACCACTATAAACAACAGTTTGGAGGTTCCTCAAGAAACTAAAAA diff --git a/perl/t/data/blat/chr10_1-23700.fa.fai b/perl/t/data/blat/chr10_1-23700.fa.fai new file mode 100644 index 0000000..d99b55d --- /dev/null +++ b/perl/t/data/blat/chr10_1-23700.fa.fai @@ -0,0 +1 @@ +chr10 23700 7 60 61 diff --git a/perl/t/data/blat/test.bam b/perl/t/data/blat/test.bam new file mode 100644 index 0000000000000000000000000000000000000000..5bf99699dae5d0161f6f807b7b125e6a9d5ab833 GIT binary patch literal 310622 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 z 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

(Bl~n+cyg z4A;v5F|ks2xgp*yezi=0b+vMEF20%Sch;y6gcPZ0mAaPH75#a)ki)^k1K9Q7Yrwo% zNWD5d7vD-Xy4u?qt=rVU-XBawpRT!~=}*?`E-q&-BDP`=M#V4YVozI7gZ)mA&~;b) z8&{niejR>$vE>V?*GsW8mxfn|uyfe;h06I%>gmo^qy6=$z3;i3cS{pw$m$Sr@}smHsY40+9-|F?D={04nAc(vfqa2ujB>QDadq&;bJ0>-6%s7Gw| z?XBPWvGS1MSe?cWq1a_-&C+@poWnkGLzJvMK80ZK7FOK55c>-KME5uKjwuJ~| z7(H9jira|Tuj7yZVC8Y0C@H2JyzuTl;F0lkwr(m(=GoI}Ex(1>6etgR)u34>ooz}k z`I-5>WyN>tTXMspJXoNFxd?8=&h@O)%D3%v(u6iQ_gqxYIoRCWo!a#JrkZRiu%f2J zR{pcSkJ4AM@8dzt@-0AX|s- zNy-NMxSjPH)fUD)44aIupR3q?brW|5U$MKB(RMT6d$$c9x7ivO*VVJ?cwj3-SF&Ol zJoQO5Z698vhup)ZNjGqmr)z4bR#l(N^qr^_r|8M3So{XuUNxS_Sxrr1iN3>LP(55M z;a6$7Um_8AXI)A0gcj}~smhm36h=RavXE|Xqg<$2L z#>JIBd==L{lPQhU2)WAbTD%St-zw8v(UI!zjlUu{1fmASLgng=&t{j}gQHM#^i=Y-Nbm|A-aL!VWc0rWIk!%2g7 zjCKF|pIC1DZK1&eQOsZzeGtq$mo1TbiIu8HRhRYfHQaKbVhhwYu6r2re$<+`jx`44 zna2#LIM>hym@>F6mOy6kM6M_YldN}|H}7Az1Z*L9P5!>p!1CBv%J*9#ZefZ^mt9@0 zRidN1oj+5Sq|-r4E1LQ0{?6azo@6>R*FUSZfb;Y3H!areXd2^>v)DFWlnwZGMA zjDqz0%JOol6{wJCx8cg#fStB89s&PtrIg)vw^6*)>FW}6w{!{dB{ZT&fU=M9Ni4&IgsZ1rdhKKWIw$%r|8H(dA${xg0$3%~~49=V^npUK}_ zl$>`oW&Hy$yYlp7v6pn)4`45bVe)002y2+FlYUAaO!3)OQs1hH6$@|Cugng6#a02I2(v zyz%iZ`3H~U6)`-@s?C<4CO&L#KmR4j#rDuZ;3NMafGiH3 zPp5MYZyzKbpu9iHO7da5o~?5}1xbOCG;A311mK*;_hlKm_2my%$V@`E8*g?@*OPSa zY&9M-5-YmvQmfnT?qwi96D%j%52b^uv76!dMD}Jy+SN3&zd#vB$cVYQfYxIjfN)k9g+3PvRz~5 zAIU$i<|?9-v8wl(+sk;{xQbuX6**bdQgzAzElVZ4MPI!6g6!|7MNChYa2V5uqxXr7 ztZv=a$bsRKGk31W=KUaW?xsb%l>~2gVT0^L4?`KM7@O%)-KauqQLo8D#e}P@1m(;4 znQtpOw=XF*>}RJawl1iJJW`^z*QjU=lPhW|(T_hi9IElmK#6Git>6Dl)4q>(uw!}z zsq=|!6vJ0B|P(QlO*c#GZWIlSZ4iS zk>dK^>2|xxGT9@-F;E4M6k%Ew+YsZPRXQH#LKB&VH^|t^EQ1$Z|9GYEPx|WP0{xd|aWa7Ulg`W}DtE;Iy@gY@3iOS(XMN7P2q z=j&zG8vdGDw7$_iNLu;cQL*&WChlxb>Nj@7!cu-nNz9c%ZOk=t!y)3QSthWCU2G7!Np-3-lV&ZP9d7?{

76=1j^KLqoDHk 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 z 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 literal 0 HcmV?d00001 diff --git a/perl/t/data/blat/test.bam.bai b/perl/t/data/blat/test.bam.bai new file mode 100644 index 0000000000000000000000000000000000000000..0905a976757c8928b7281c54cf2e2c0a0348be53 GIT binary patch literal 27048 zcmeIuyA8rH6a~;{AQhsaqoJf{grrNxVgz +# +# This file is part of cgpPindel. +# +# cgpPindel is free software: you can redistribute it and/or modify it under +# the terms of the GNU Affero 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 Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +########## LICENCE ########## + +use strict; +use File::Temp qw(tempdir); +use Test::More; +use Test::Fatal; +use File::Spec::Functions; +use Const::Fast qw(const); +use FindBin qw($Bin); + +const my $MODULE => 'Sanger::CGP::Pindel::OutputGen::VcfBlatAugment'; +const my $DATA => "$Bin/data/blat"; +const my @HEADER_ENDS => do { + no warnings 'qw'; + qw(#CHROM POS ID REF ALT QUAL FILTER INFO FORMAT PD26988a); +}; +const my $HEADER_LINES => 3391; + +const my $DATA_ARR_D => [qw(chr10 11201 id CA C 390 . PC=D;RS=11201;RE=11205;LEN=1;S1=11;S2=849.236;REP=3 GT:PP:NP ./.:10:0)]; +const my $RES_ARR_D => [13, 3, 9, 3, 0.429]; +const my $DATA_ARR_DI => [qw(chr10 22777 id AGAAACTGTG ACTGTGAGATAGATATATATAGATAGATATAT 105 . PC=DI;RS=22777;RE=22787;LEN=9;S1=6;REP=0 GT:PP:NP ./.:0:5)]; +const my $RES_ARR_DI => [2, 3, 2, 9, 0.688]; +const my $DATA_ARR_SI => [qw(chr10 11643 id C CG 150 . PC=I;RS=11643;RE=11649;LEN=1;S1=6;S2=421.908;REP=4 GT:PP:NP ./.:0:5)]; +const my $RES_ARR_SI => [7, 14, 7, 10, 0.447]; + +my ($stdout_fh, $buffer); + +subtest 'Initialisation checks' => sub { + use_ok($MODULE); + new_vba(catfile($DATA, 'D.vcf')); +}; + +subtest 'Header checks' => sub { + my $vba = new_vba(catfile($DATA, 'D.vcf')); + ok($vba->output_header); + my @lines = split /\n/, $buffer; + is(scalar @lines, $HEADER_LINES, 'Expected number of header lines'); + is($lines[-1], join("\t", @HEADER_ENDS), 'Expected final header line'); +}; + +subtest 'Simple Deletion checks' => sub { + my $vba = new_vba(catfile($DATA, 'D.vcf')); + my $v_h = $vba->to_data_hash($DATA_ARR_D); + my @gt_out = $vba->blat_record($v_h); + is_deeply(\@gt_out, $RES_ARR_D); +}; + +subtest 'Simple Insertion checks' => sub { + my $vba = new_vba(catfile($DATA, 'SI.vcf')); + my $v_h = $vba->to_data_hash($DATA_ARR_SI); + my @gt_out = $vba->blat_record($v_h); + is_deeply(\@gt_out, $RES_ARR_SI); +}; + +subtest 'Complex event checks' => sub { + my $vba = new_vba(catfile($DATA, 'DI.vcf')); + my $v_h = $vba->to_data_hash($DATA_ARR_DI); + my @gt_out = $vba->blat_record($v_h); + is_deeply(\@gt_out, $RES_ARR_DI); +}; + +done_testing(); + + +sub new_vba { + my $vcf = shift; + return new_ok($MODULE, [ + input => $vcf, + ref => catfile($DATA, 'chr10_1-23700.fa'), + ofh => buffer_fh(), + hts_file => catfile($DATA, 'test.bam'), + ]); +} + +sub buffer_fh { + if(defined $stdout_fh) { + close $stdout_fh; + } + $buffer = q{}; + open $stdout_fh, ">", \$buffer or die $!; + return $stdout_fh; +} + + + +__END__ +subtest 'corrupt_pindel_input checks' => sub { + # File of correct size (no NUL) + is( Sanger::CGP::Pindel::InputGen::corrupt_pindel_input("$DATA/inputGen-goodfile.txt.gz", 22), + undef, + 'corrupt_pindel_input - well formed compressed file, expected size'); + # File of incorrect size (no NUL) + is( Sanger::CGP::Pindel::InputGen::corrupt_pindel_input("$DATA/inputGen-goodfile.txt.gz", 9), + "$DATA/inputGen-goodfile.txt.gz", + 'corrupt_pindel_input - well formed compressed file, UNexpected size'); + # File of incorrect size + NUL + is( Sanger::CGP::Pindel::InputGen::corrupt_pindel_input("$DATA/inputGen-NUL.txt.gz", 22), + "$DATA/inputGen-NUL.txt.gz", + 'corrupt_pindel_input - NUL character in compressed file, expected size'); + # File of correct size + NUL + is( Sanger::CGP::Pindel::InputGen::corrupt_pindel_input("$DATA/inputGen-NUL.txt.gz", 9), + "$DATA/inputGen-NUL.txt.gz", + 'corrupt_pindel_input - NUL character in compressed file, UNexpected size'); +}; + +subtest 'reads_to_disk checks' => sub{ + $obj = new_ok($MODULE, + [ "$DATA/test.bam", + undef, + "$DATA/genome_22.fa"]); + my $out_folder = tempdir( 'pindelTests_XXXX', CLEANUP => 1 ); + + $obj->set_outdir($out_folder); + ok($obj->reads_to_disk($RECORD_SET), 'create output'); + is($obj->{'rname_bytes'}->{'22'}, $RECORD_OUT_BYTES, 'Verify bytes written captured'); + + ok($obj->validate, 'validate returns true') +}; + +done_testing(); + From 49ecb5f884fbd8e71ef7caecdaf1b3c851b1eef7 Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Tue, 12 May 2020 13:51:58 +0000 Subject: [PATCH 06/60] Functional cohort sample code with blat and BAM outputs --- .gitignore | 1 + perl/bin/pindelCohort.pl | 2 +- perl/bin/pindel_blat_vaf.pl | 21 ++- perl/lib/Sanger/CGP/Pindel/Implement.pm | 30 ++++- .../CGP/Pindel/OutputGen/VcfBlatAugment.pm | 122 +++++++++++++----- perl/t/vcfBlatAugment.t | 19 ++- 6 files changed, 149 insertions(+), 46 deletions(-) diff --git a/.gitignore b/.gitignore index 6a3bb72..4203ca7 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ /perl/Makefile /perl/pm_to_blib .idea/* +/python/env diff --git a/perl/bin/pindelCohort.pl b/perl/bin/pindelCohort.pl index a939c24..27a2e48 100644 --- a/perl/bin/pindelCohort.pl +++ b/perl/bin/pindelCohort.pl @@ -160,7 +160,7 @@ =head1 SYNOPSIS -reference -r Path to reference genome file *.fa[.gz] Optional - -all Generate BLAT counts for all samples regardless of pindel call state. + -pad Multiples (>=1) of max readlength to pad blat target seq with [default 1] -seqtype -st Sequencing protocol, expect all input to match [WGS] -assembly -as Name of assembly in use - when not available in BAM header SQ line. diff --git a/perl/bin/pindel_blat_vaf.pl b/perl/bin/pindel_blat_vaf.pl index ab86b1c..5f92419 100755 --- a/perl/bin/pindel_blat_vaf.pl +++ b/perl/bin/pindel_blat_vaf.pl @@ -18,7 +18,9 @@ input => $options->{input}, ref => $options->{ref}, ofh => $options->{output}, + sam => $options->{align}, hts_file => $options->{hts}, + pad_mult => $options->{pad}, ); $augment->output_header; $augment->process_records; @@ -32,9 +34,11 @@ sub setup{ GetOptions( 'h|help' => \$opts{h}, 'm|man' => \$opts{m}, 'v|version' => \$opts{v}, - 'o|output:s' => \$opts{output}, + 'o|output=s' => \$opts{output}, + 'a|align=s' => \$opts{align}, 'r|ref=s' => \$opts{ref}, 'i|input=s' => \$opts{input}, + 'p|pad:f' => \$opts{pad}, 'd|debug' => \$opts{debug}, 'hts=s' => \$opts{hts}, ); @@ -57,11 +61,12 @@ sub setup{ die "ERROR: Unable to find *.bas file for $opts{hts}\n"; } - if($opts{'output'}) { - open my $fh, '>', $opts{output}; - $opts{output} = $fh; - } - else { $opts{output} = \*STDOUT; } + $opts{align} = $opts{output}.'.sam' unless(defined $opts{align}); + + open my $ofh, '>', $opts{output}; + $opts{output} = $ofh; + open my $sfh, '>', $opts{align}; + $opts{align} = $sfh; return \%opts; } @@ -82,9 +87,11 @@ =head1 SYNOPSIS -ref -r File path to the reference file used to provide the coordinate system. -input -i VCF file to read in. -hts BAM/CRAM file for associated sample. + -output -o File path for VCF output (not compressed) Optional parameters: - -output -o File path to output to. Defaults to STDOUT. + -align -a File path for accepted alignments (SAM records, no header) [defaults to -o .sam] + -pad -p Multiples (>=1) of max readlength to pad blat target seq with [default 1] Other: -help -h Brief help message. diff --git a/perl/lib/Sanger/CGP/Pindel/Implement.pm b/perl/lib/Sanger/CGP/Pindel/Implement.pm index 3989fe2..3e90101 100644 --- a/perl/lib/Sanger/CGP/Pindel/Implement.pm +++ b/perl/lib/Sanger/CGP/Pindel/Implement.pm @@ -258,15 +258,17 @@ sub concat { my $tmp = $options->{'tmp'}; my $vcf = File::Spec->catdir($tmp, 'vcf'); - my $sample_name = (PCAP::Bam::sample_name($options->{'hts_files'}->[0]))[0]; + my $hts_input = $options->{'hts_files'}->[0]; + my $sample_name = (PCAP::Bam::sample_name($hts_input))[0]; my $vcf_gz = File::Spec->catfile($options->{'outdir'}, sprintf('%s.pindel.vcf.gz', $sample_name)); + my $bam = File::Spec->catfile($options->{'outdir'}, sprintf('%s.pindel.bam', $sample_name)); unless(PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), 'concat')) { #vcf-concat blat_*.vcf my $command = _which('vcf-concat'); $command .= sprintf q{ %s | bgzip -c > %s}, File::Spec->catfile($vcf, 'blat_*.vcf'), $vcf_gz; - PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), $command, 'concat'); + PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), ['set -o pipefail', $command], 'concat'); PCAP::Threaded::touch_success(File::Spec->catdir($tmp, 'progress'), 'concat'); } unless(PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), 'tabix')) { @@ -275,6 +277,23 @@ sub concat { PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), $command, 'tabix'); PCAP::Threaded::touch_success(File::Spec->catdir($tmp, 'progress'), 'tabix'); } + # now deal with the sam files + unless(PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), 'calmd')) { + my $samtools = _which('samtools'); + my $command = sprintf q{(%s view -H %s | grep -P '^@(HD|SQ)' && sort %s | uniq) | %s sort -l 0 -T %s - | %s calmd -b - %s > %s}, + $samtools, $hts_input, + File::Spec->catfile($vcf, 'blat_*.vcf.sam'), + $samtools, File::Spec->catfile($vcf, 'srt'), + $samtools, $options->{'reference'}, $bam; + PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), ['set -o pipefail', $command], 'calmd'); + PCAP::Threaded::touch_success(File::Spec->catdir($tmp, 'progress'), 'calmd'); + } + unless(PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), 'index')) { + my $command = _which('samtools'); + $command .= sprintf q{ index %s}, $bam; + PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), $command, 'index'); + PCAP::Threaded::touch_success(File::Spec->catdir($tmp, 'progress'), 'index'); + } return 1; } @@ -303,11 +322,12 @@ sub blat { my $blat_file = $split_file; $blat_file =~ s/split_([a-z]+)/blat_$1/; my $command = $^X.' '._which('pindel_blat_vaf.pl'); - $command .= sprintf q{ -r %s -hts %s -i %s -o %s}, + $command .= sprintf q{ -r %s -hts %s -i %s -o %s -p %s}, $options->{'reference'}, $options->{hts_files}->[0], $split_file, - $blat_file; + $blat_file, + $options->{pad}; PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), $command, $index); PCAP::Threaded::touch_success(File::Spec->catdir($tmp, 'progress'), $index); @@ -619,7 +639,7 @@ sub shared_setup { 'd|debug' => \$opts{'debug'}, 'a|apid:s' => \$opts{'apid'}, # specifically for cohort - 'all' => \$opts{'all'}, + 'pad:f' => \$opts{'pad'}, ); for my $opt_key(keys %{$extra_opts}) { my $v = $extra_opts->{$opt_key}; diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm index b8665a4..b431a58 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm @@ -47,6 +47,7 @@ const my $READS_AND_BLAT => q{bash -c 'set -o pipefail ; samtools view -uF 3840 # returns +ve and then -ve results const my $SAM_DEPTH_PN => q{bash -c "set -o pipefail ; samtools view -uF 3844 %s %s | pee 'samtools view -c -F 16 -' 'samtools view -c -f 16 -'"}; const my $LOCI_FMT => '%s:%d-%d'; +const my $TARGET_PAD_MULTIPLIER => 1; 1; @@ -58,8 +59,10 @@ sub new{ my $self = { input => $args{input}, ref => $args{ref}, - ofh => $args{ofh}, + ofh => $args{ofh}, # vcf + sfh => $args{sam}, # sam reads hts_file => $args{hts_file}, + target_pad_multiplier => $args{pad_mult} || $TARGET_PAD_MULTIPLIER, }; bless $self, $class; $self->_init; @@ -120,7 +123,7 @@ sub _buffer_sizes { } } $self->{max_insert} = $max_ins; - $self->{max_rl} = $max_rl; + $self->{target_pad} = int($max_rl * $self->{target_pad_multiplier}); return $sample; } @@ -157,10 +160,12 @@ sub _add_headers { $vcf->add_header_line({'key'=>'source', 'value' => basename($0)}, 'append' => 1); my @format = ( - {key => 'FORMAT', ID => 'WTP', Number => 1, Type => 'Integer', Description => '+ve strand reads BLATed (or count for large deletions) to reference at this location'}, - {key => 'FORMAT', ID => 'WTN', Number => 1, Type => 'Integer', Description => '-ve strand reads BLATed (or count for large deletions) to reference at this location'}, + {key => 'FORMAT', ID => 'WTP', Number => 1, Type => 'Integer', Description => '+ve strand reads BLATed (or count for large deletions) to reference sequence at this location'}, + {key => 'FORMAT', ID => 'WTN', Number => 1, Type => 'Integer', Description => '-ve strand reads BLATed (or count for large deletions) to reference sequence at this location'}, + {key => 'FORMAT', ID => 'WTM', Number => 1, Type => 'Float', Description => 'Mismatch fraction of reads BLATed to reference sequence at this location (to 3 d.p.)'}, {key => 'FORMAT', ID => 'MTP', Number => 1, Type => 'Integer', Description => '+ve strand reads BLATed to alternate sequence at this location'}, {key => 'FORMAT', ID => 'MTN', Number => 1, Type => 'Integer', Description => '-ve strand reads BLATed to alternate sequence at this location'}, + {key => 'FORMAT', ID => 'MTM', Number => 1, Type => 'Float', Description => 'Mismatch fraction of reads BLATed to alternate sequence at this location (to 3 d.p.)'}, {key => 'FORMAT', ID => 'VAF', Number => 1, Type => 'Float', Description => 'Variant allele fraction using reads that unambiguously map to ref or alt seq (to 3 d.p.)'}, ); $self->{fmt_ext} = q{}; @@ -217,18 +222,14 @@ sub process_records { my $fh = $self->{ofh}; my $count=0; while(my $v_d = $self->{vcf}->next_data_array) { -$count++; -#next if($count != 8); # important the one relating to the bug -#next if($count != 21247); -#printf "%s\n", join "\t", @{$v_d}; +#$count++; +#next if($count != 1); my $v_h = $self->to_data_hash($v_d); -#next if($v_h->{'POS'} != 49092625); my @extra_gt = $self->blat_record($v_h); $v_d->[$V_FMT] .= $self->{fmt_ext}; $v_d->[$V_GT] = join q{:}, $v_d->[$V_GT], @extra_gt; printf $fh "%s\n", join "\t", @{$v_d}; #last; -#last if($count == 250); } } @@ -276,7 +277,6 @@ sub blat_reads { close $fh_psl or die "Failed to close $file_psl (psl output)"; my $c_blat = sprintf $READS_AND_BLAT, $self->{hts_file}, $self->read_ranges($v_h), $file_query, $file_target, $file_query, $file_psl, $file_psl, $file_target, $file_query; -#print "$c_blat\n"; my ($c_out, $c_err, $c_exit) = capture { system($c_blat); }; if($c_exit) { warn "An error occurred while executing $c_blat\n"; @@ -284,21 +284,29 @@ sub blat_reads { exit $c_exit; } -# print "query.fa\n"; +## redirect output and the following will give you relevant files and the command +## $ tail -n 5 output | head -n 4 > target.fa && head -n -5 output > query.fa && tail -n 1 output #system("cat $file_query"); -# print "target.fa\n"; #system("cat $file_target"); -#print "\n$c_out\n"; -# exit 1; +#print "$c_blat\n"; - my ($wtp, $wtn, $mtp, $mtn) = $self->psl_axt_parser(\$c_out, $v_h); + my ($wtp, $wtn, $mtp, $mtn, $wt_bmm, $mt_bmm) = $self->psl_axt_parser(\$c_out, $v_h); + my ($wtm, $mtm) = (q{.}, q{.}); + my $wtr = $wtp + $wtn; if($wtp + $wtn == 0) { ($wtp, $wtn) = $self->sam_depth($v_h); + $wtr = $wtp + $wtn; + } + else { + $wtm = sprintf("%.3f", $wt_bmm / $wtr); } my $mtr = $mtp+$mtn; - my $depth = $wtp+$wtn+$mtr; + if($mtr > 0) { + $mtm = sprintf("%.3f", $mt_bmm / $mtr); + } + my $depth = $wtr+$mtr; my $vaf = $depth ? sprintf("%.3f", $mtr/$depth) : 0; - return ($wtp, $wtn, $mtp, $mtn, $vaf); + return ($wtp, $wtn, $wtm, $mtp, $mtn, $mtm, $vaf); } sub psl_axt_parser { @@ -310,13 +318,16 @@ sub psl_axt_parser { my %reads; for(my $i = 0; $i<$line_c; $i+=4) { my ($id, $t_name, $t_start, $t_end, $q_name, $q_start, $q_end, $strand, $score) = split q{ }, $lines[$i]; - push @{$reads{$q_name}{$score}}, [$t_name, $t_start, $t_end, $q_name, $q_start, $q_end, $strand, $score, $lines[$i+1], $lines[$i+2]]; + my $clean_qname = $q_name; + $clean_qname =~ s{/([12])$}{}; + push @{$reads{$clean_qname}{$score}}, [$t_name, $t_start, $t_end, $q_name, $q_start, $q_end, $strand, $score, $lines[$i+1], $lines[$i+2]]; } #print Dumper(\%reads); my $SKIP_EVENT = q{}; - my %TYPE_STRAND; + my %type_strand; + my %bmm_sums; # sort keys for consistency READ: for my $read(sort keys %reads) { # get the alignment with the highest score @@ -328,18 +339,29 @@ my $SKIP_EVENT = q{}; } my $record = $records[0]; if($self->parse_axt_event($v_h, $record) == 1) { - $TYPE_STRAND{$record->[0].$record->[6]} += 1; + $type_strand{$record->[0].$record->[6]} += 1; + $bmm_sums{$record->[0]} += bmm($record->[8], $record->[9]); } #$SKIP_EVENT = unless($SKIP_EVENT eq q{1}); #chomp $SKIP_EVENT; next READ; # remaining items are worse alignments } } - my $wtp = $TYPE_STRAND{'REF+'} || 0; - my $wtn = $TYPE_STRAND{'REF-'} || 0; - my $mtp = $TYPE_STRAND{'ALT+'} || 0; - my $mtn = $TYPE_STRAND{'ALT-'} || 0; - return ($wtp, $wtn, $mtp, $mtn); + my $wtp = $type_strand{'REF+'} || 0; + my $wtn = $type_strand{'REF-'} || 0; + my $mtp = $type_strand{'ALT+'} || 0; + my $mtn = $type_strand{'ALT-'} || 0; + return ($wtp, $wtn, $mtp, $mtn, $bmm_sums{REF}, $bmm_sums{ALT}); +} + +sub bmm { + my ($a, $b) = @_; # need a copy of the strings anyway + my $len = length $a; + my $diffs = 0; + for(0..($len-1)) { + $diffs++ if(chop $a ne chop $b); + } + return $diffs/$len; } sub parse_axt_event { @@ -379,11 +401,13 @@ sub parse_axt_event { if($exp_pos <= length $q_seq) { my $sub_q_seq = substr($q_seq, $exp_pos, length $change_seq); if(length $change_seq == length $sub_q_seq # same length + && index($t_seq, q{-}) == -1 # no gaps && index($q_seq, q{-}) == -1 # no gaps && substr($change_seq,0,1) eq substr($sub_q_seq,0,1) # matching first base && substr($change_seq,-1,1) eq substr($sub_q_seq,-1,1) # matching last base ) { $retval = 1; + $self->sam_record($v_h, $rec); } } } @@ -391,6 +415,47 @@ sub parse_axt_event { return $retval; } +sub sam_record { + my($self, $v_h, $rec) = @_; +# warn Dumper($v_h); +# warn Dumper($rec); + + my $qname = $rec->[3]; + my $seq = $rec->[9]; + my $flag = 0; # not paired + $flag += 16 if($rec->[6] eq '-'); + + # POS is the base preceeing any change, seq start it this - target_pad + my $pos = ($v_h->{POS} - $self->{target_pad}) + $rec->[1]; + my $cigar = q{}; + if($rec->[0] eq 'REF') { + $cigar = length($seq).'M'; + } + else { + my $m_c = ($v_h->{change_pos} - $rec->[1]) + 1; + $m_c += 1 if($v_h->{PC} eq 'I'); + $cigar = $m_c.'M'; +#print $cigar."\n"; + my $change_ref = length($v_h->{REF}); + my $change_alt = length($v_h->{ALT}); + if($change_ref) { + $cigar .= $change_ref.'D'; +#print $cigar."\n"; + } + if($change_alt) { + $cigar .= $change_alt.'I'; +#print $cigar."\n"; + $m_c += $change_alt; # as consumes read + } + $cigar .= (length($seq) - $m_c).'M'; +#print $cigar."\n"; + } +#printf "%s:%d-%d\n", $v_h->{CHROM}, $pos, $pos + 100; + printf {$self->{sfh}} "%s\n", join "\t", $qname, $flag, $v_h->{CHROM}, $pos, 60, $cigar, '*', 0, 0, $seq, '*'; + +# ; +} + sub sam_depth { my ($self, $v_h) = @_; my $mid_point = int ($v_h->{RS} + (($v_h->{RE} - $v_h->{RS})*0.5)); @@ -407,11 +472,10 @@ sub sam_depth { sub flanking_ref { my ($self, $v_h) = @_; - # use max_rl to extend before and after the range_start/end my $ref_left = $self->{fai}->get_sequence_no_length( sprintf $LOCI_FMT, $v_h->{CHROM}, - ($v_h->{POS} - $self->{max_rl})+1, + ($v_h->{POS} - $self->{target_pad})+1, $v_h->{POS}, ); #print "$ref_left\n"; @@ -420,7 +484,7 @@ sub flanking_ref { sprintf $LOCI_FMT, $v_h->{CHROM}, $v_h->{END}, - $v_h->{END} + $self->{max_rl}, + $v_h->{END} + $self->{target_pad}, ); #print "$ref_right\n"; return [$ref_left, $ref_right] diff --git a/perl/t/vcfBlatAugment.t b/perl/t/vcfBlatAugment.t index 5bb4934..7ad1ebf 100644 --- a/perl/t/vcfBlatAugment.t +++ b/perl/t/vcfBlatAugment.t @@ -33,16 +33,17 @@ const my @HEADER_ENDS => do { no warnings 'qw'; qw(#CHROM POS ID REF ALT QUAL FILTER INFO FORMAT PD26988a); }; -const my $HEADER_LINES => 3391; +const my $HEADER_LINES => 3393; const my $DATA_ARR_D => [qw(chr10 11201 id CA C 390 . PC=D;RS=11201;RE=11205;LEN=1;S1=11;S2=849.236;REP=3 GT:PP:NP ./.:10:0)]; -const my $RES_ARR_D => [13, 3, 9, 3, 0.429]; +const my $RES_ARR_D => [13, 3, 0.016, 9, 3, 0.007, 0.429]; const my $DATA_ARR_DI => [qw(chr10 22777 id AGAAACTGTG ACTGTGAGATAGATATATATAGATAGATATAT 105 . PC=DI;RS=22777;RE=22787;LEN=9;S1=6;REP=0 GT:PP:NP ./.:0:5)]; -const my $RES_ARR_DI => [2, 3, 2, 9, 0.688]; +const my $RES_ARR_DI => [2, 3, 0.001, 2, 8, 0.017, 0.667]; const my $DATA_ARR_SI => [qw(chr10 11643 id C CG 150 . PC=I;RS=11643;RE=11649;LEN=1;S1=6;S2=421.908;REP=4 GT:PP:NP ./.:0:5)]; -const my $RES_ARR_SI => [7, 14, 7, 10, 0.447]; +const my $RES_ARR_SI => [7, 14, 0.014, 7, 10, 0.005, 0.447]; my ($stdout_fh, $buffer); +my ($sam_stdout_fh, $sam_buffer); subtest 'Initialisation checks' => sub { use_ok($MODULE); @@ -87,6 +88,7 @@ sub new_vba { input => $vcf, ref => catfile($DATA, 'chr10_1-23700.fa'), ofh => buffer_fh(), + sam => sam_buffer_fh(), hts_file => catfile($DATA, 'test.bam'), ]); } @@ -100,6 +102,15 @@ sub buffer_fh { return $stdout_fh; } +sub sam_buffer_fh { + if(defined $sam_stdout_fh) { + close $sam_stdout_fh; + } + $sam_buffer = q{}; + open $sam_stdout_fh, ">", \$sam_buffer or die $!; + return $sam_stdout_fh; +} + __END__ From 79a822fa6cd601f4fe0558b8f8e16f2313303e64 Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Tue, 12 May 2020 13:54:04 +0000 Subject: [PATCH 07/60] Script for assessing impact of different lengths of target seq --- python/pindelMmPlots.py | 75 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100755 python/pindelMmPlots.py diff --git a/python/pindelMmPlots.py b/python/pindelMmPlots.py new file mode 100755 index 0000000..8882e24 --- /dev/null +++ b/python/pindelMmPlots.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 + +# core libs +import os # for mkdir, path stuff +import sys +import argparse + +# requirements +import vcfpy +import pandas as pd +import seaborn as sns + +def parse_vcf(df_list, rl, vcfin): + reader = vcfpy.Reader.from_path(vcfin) + sample = reader.header.samples.names[0] + for record in reader: + #count +=1 + #if count > 1000: + # break + if record.INFO['PC'] != args.type: + continue + call = record.call_for_sample[sample] + if call.data.get('MTP') is None and call.data.get('MTN'): + continue + df_list.append({'CHROM': record.CHROM, 'RL': rl, 'DT': 'WT', 'Mismatch': call.data.get('WTM') or 0, 'POS_READS': call.data.get('WTP'), 'NEG_READS': call.data.get('WTN')}) + df_list.append({'CHROM': record.CHROM, 'RL': rl, 'DT': 'MT', 'Mismatch': call.data.get('MTM') or 0, 'POS_READS': call.data.get('MTP'), 'NEG_READS': call.data.get('MTN')}) + + if call.data.get('WTP') > 300: + print(f'{record.CHROM}:{record.POS}..{record.POS}\t{record.CHROM}:{record.POS}-{record.POS}') + +def draw_boxplot(df_list: list, x_item: str, y_item: str, hue: str, out_file: str, ylim=None): + plot = sns.boxplot( + x=x_item, y=y_item, + data=pd.DataFrame.from_records(df_list), + palette="colorblind", + hue=hue, + ) + if ylim: + plot.set(ylim=ylim) + fig = plot.get_figure() + fig.savefig(out_file) + fig.clf() # this clears the figure + + +parser = argparse.ArgumentParser(description='Generate wisker plots of mismatch rates for a set of vcfs') +parser.add_argument('-d', '--dir', dest='outDir', metavar='outDir', help='Directory for output', required=True) +parser.add_argument('-l', '-labels', dest='labels', metavar='1.0,...', help='CSV of readlength multipliers, same order as vcfs they apply to', required=True) +parser.add_argument('-t', '-type', dest='type', choices=['D','DI','I'], help='Pindel data type (PC=?)', required=True) +parser.add_argument('-f', '--format', dest='imgFormat', choices=['png','pdf','svg'], help='Format to save venn diagram', required=False, default='png') +parser.add_argument('vcfs', nargs='+') + +args = parser.parse_args() + +# build the output folder before starting work +if os.path.exists(args.outDir) is False: + os.mkdir(args.outDir, mode=0o700) # rwx owner only + +### split the labels and create map with vcfs +r_lengths = args.labels.split(',') +if len(r_lengths) != len(args.vcfs): + sys.exit('Error: "-labels" needs to have the same number of elements as the number of vcfs supplied.') +vcfs = {} +for l, v in zip(r_lengths, args.vcfs): + vcfs[float(l)] = v + +df_list = [] +for vcf_set in vcfs.items(): + print(f'Processing read length multiplier {vcf_set[0]} for type {args.type}') + parse_vcf(df_list, vcf_set[0], vcf_set[1]) + +draw_boxplot(df_list, 'RL', 'Mismatch', 'DT', args.type+'_mm.png') #, ylim=(0, 0.05)) +draw_boxplot(df_list, 'RL', 'POS_READS', 'DT', args.type+'_pos.png') #, ylim=(0, 0.05)) +draw_boxplot(df_list, 'RL', 'NEG_READS', 'DT', args.type+'_neg.png') #, ylim=(0, 0.05)) + + From e831ae651fecae904d763f4dcf8439bdf099fb85 Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Tue, 12 May 2020 15:22:13 +0000 Subject: [PATCH 08/60] Correct missing param default --- perl/bin/pindelCohort.pl | 1 + 1 file changed, 1 insertion(+) diff --git a/perl/bin/pindelCohort.pl b/perl/bin/pindelCohort.pl index 27a2e48..6d95de6 100644 --- a/perl/bin/pindelCohort.pl +++ b/perl/bin/pindelCohort.pl @@ -137,6 +137,7 @@ sub index_check { sub setup { my $opts = Sanger::CGP::Pindel::Implement::shared_setup([],{}); + $opts->{pad} = 1 unless(exists $opts->{pad} && defined $opts->{pad}); # add hts_files from the remains of @ARGV Sanger::CGP::Pindel::Implement::cohort_files($opts); From e8f7868c8b2049c1e78b474d680e14254de98c15 Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Thu, 14 May 2020 12:09:26 +0000 Subject: [PATCH 09/60] minor fixes --- .../CGP/Pindel/OutputGen/VcfBlatAugment.pm | 52 ++++++++++++++----- perl/t/vcfBlatAugment.t | 6 +-- 2 files changed, 42 insertions(+), 16 deletions(-) diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm index b431a58..879609c 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm @@ -26,7 +26,7 @@ use warnings FATAL => 'all'; use autodie qw(:all); use Const::Fast qw(const); use File::Basename; -use List::Util qw(max); +use List::Util qw(min max); use File::Temp qw(tempfile); use Capture::Tiny qw(capture); use Vcf; @@ -48,6 +48,8 @@ const my $READS_AND_BLAT => q{bash -c 'set -o pipefail ; samtools view -uF 3840 const my $SAM_DEPTH_PN => q{bash -c "set -o pipefail ; samtools view -uF 3844 %s %s | pee 'samtools view -c -F 16 -' 'samtools view -c -f 16 -'"}; const my $LOCI_FMT => '%s:%d-%d'; const my $TARGET_PAD_MULTIPLIER => 1; +const my $PAD_EVENT => 3; +const my $MAPPED_RL_MULT => 0.6; 1; @@ -62,9 +64,14 @@ sub new{ ofh => $args{ofh}, # vcf sfh => $args{sam}, # sam reads hts_file => $args{hts_file}, - target_pad_multiplier => $args{pad_mult} || $TARGET_PAD_MULTIPLIER, + target_pad_multiplier => $TARGET_PAD_MULTIPLIER, }; bless $self, $class; + + if(exists $args{pad_mult} && defined $args{pad_mult}) { + $self->{target_pad_multiplier} = $args{pad_mult} + 0; + } + $self->_init; return $self; @@ -108,12 +115,15 @@ sub _buffer_sizes { my $b = PCAP::Bam::Bas->new($self->{hts_file}.'.bas'); my $max_ins = 0; my $max_rl = 0; + my $min_rl = 1_000_000; my $sample; for my $rg($b->read_groups) { - my $m_sd = $b->get($rg, 'mean_insert_size') + ($b->get($rg, 'insert_size_sd') * $SD_MULT); + my $m_sd = int ($b->get($rg, 'mean_insert_size') + ($b->get($rg, 'insert_size_sd') * $SD_MULT)); $max_ins = $m_sd if($m_sd > $max_ins); - my $rl = max ($b->get($rg, 'read_length_r1'), $b->get($rg, 'read_length_r2')); - $max_rl = $rl if($rl > $max_rl); + my $tmp_max = max ($b->get($rg, 'read_length_r1'), $b->get($rg, 'read_length_r2')); + my $tmp_min = min ($b->get($rg, 'read_length_r1'), $b->get($rg, 'read_length_r2')); + $min_rl = $tmp_min if($tmp_min < $min_rl); + $max_rl = $tmp_max if($tmp_max > $max_rl); my $s = $b->get($rg, 'sample'); if($sample) { die "ERROR: Multiple samples found in %s\n", $self->{hts}.'.bas' if($sample ne $s) @@ -122,8 +132,17 @@ sub _buffer_sizes { $sample = $s; } } + $self->{min_rl} = $min_rl; $self->{max_insert} = $max_ins; - $self->{target_pad} = int($max_rl * $self->{target_pad_multiplier}); +#printf "Defined: %d\n", $max_rl + $max_ins; +#printf "Pad mult: %d\n", int($max_rl * $self->{target_pad_multiplier}); + if($self->{target_pad_multiplier} == 0) { + $self->{target_pad} = $max_rl + $max_ins; + } + else { + $self->{target_pad} = int($max_rl * $self->{target_pad_multiplier}); + } +#printf "target_pad: %d\n", $self->{target_pad}; return $sample; } @@ -236,7 +255,7 @@ my $count=0; sub blat_record { my ($self, $v_h) = @_; # now attempt the blat stuff - my ($fh_target, $file_target) = tempfile( DIR => '/dev/shm', SUFFIX => '.fa', UNLINK => 1 ); + my ($fh_target, $file_target) = tempfile( SUFFIX => '.fa', UNLINK => 1 ); $self->blat_ref_alt($fh_target, $v_h); close $fh_target or die "Failed to close blat ref temp file"; @@ -271,9 +290,9 @@ sub read_ranges { sub blat_reads { my ($self, $v_h, $file_target) = @_; # setup ther temp files - my ($fh_query, $file_query) = tempfile( DIR => '/dev/shm', SUFFIX => '.fa', UNLINK => 1); + my ($fh_query, $file_query) = tempfile( SUFFIX => '.fa', UNLINK => 1); close $fh_query or die "Failed to close $file_query (query reads)"; - my ($fh_psl, $file_psl) = tempfile(DIR => '/dev/shm', SUFFIX => '.psl', UNLINK => 1); + my ($fh_psl, $file_psl) = tempfile( SUFFIX => '.psl', UNLINK => 1); close $fh_psl or die "Failed to close $file_psl (psl output)"; my $c_blat = sprintf $READS_AND_BLAT, $self->{hts_file}, $self->read_ranges($v_h), $file_query, $file_target, $file_query, $file_psl, $file_psl, $file_target, $file_query; @@ -289,6 +308,7 @@ sub blat_reads { #system("cat $file_query"); #system("cat $file_target"); #print "$c_blat\n"; +#print "$c_out\n"; my ($wtp, $wtn, $mtp, $mtn, $wt_bmm, $mt_bmm) = $self->psl_axt_parser(\$c_out, $v_h); my ($wtm, $mtm) = (q{.}, q{.}); @@ -318,9 +338,12 @@ sub psl_axt_parser { my %reads; for(my $i = 0; $i<$line_c; $i+=4) { my ($id, $t_name, $t_start, $t_end, $q_name, $q_start, $q_end, $strand, $score) = split q{ }, $lines[$i]; + next if($score < 0); # it happens my $clean_qname = $q_name; $clean_qname =~ s{/([12])$}{}; - push @{$reads{$clean_qname}{$score}}, [$t_name, $t_start, $t_end, $q_name, $q_start, $q_end, $strand, $score, $lines[$i+1], $lines[$i+2]]; + my $q_seq = $lines[$i+2]; + next if(length $q_seq < int $self->{min_rl} * $MAPPED_RL_MULT); + push @{$reads{$clean_qname}{$score}}, [$t_name, $t_start, $t_end, $q_name, $q_start, $q_end, $strand, $score, $lines[$i+1], $q_seq]; } #print Dumper(\%reads); @@ -335,6 +358,8 @@ my $SKIP_EVENT = q{}; my @records = @{$reads{$read}{$score}}; if(@records != 1) { # if best score has more than one alignment it is irrelevant #print Dumper(\@records); +#print "MULTI MAX\n"; +#; next READ; } my $record = $records[0]; @@ -351,6 +376,8 @@ my $SKIP_EVENT = q{}; my $wtn = $type_strand{'REF-'} || 0; my $mtp = $type_strand{'ALT+'} || 0; my $mtn = $type_strand{'ALT-'} || 0; +#print "waiting...\n"; +#; return ($wtp, $wtn, $mtp, $mtn, $bmm_sums{REF}, $bmm_sums{ALT}); } @@ -393,9 +420,9 @@ sub parse_axt_event { # }; print "OUT OF BOUNDS\n" if $@; - # all the reads that don't span the range + # all the reads that span the range are kept my $retval = 0; - if($t_start <= $v_h->{change_pos_low} && $t_end > $change_pos_high) { + if($t_start <= ($v_h->{change_pos_low} - $PAD_EVENT) && $t_end > ($change_pos_high + $PAD_EVENT)) { # look for the change (or absence) where we expect it my $exp_pos = $v_h->{change_pos_low} - $t_start; if($exp_pos <= length $q_seq) { @@ -543,7 +570,6 @@ sub blat_ref_alt { $v_h->{change_pos} = $change_at; $v_h->{change_ref} = $change_ref; $v_h->{change_alt} = $change_alt; - return 1; } diff --git a/perl/t/vcfBlatAugment.t b/perl/t/vcfBlatAugment.t index 7ad1ebf..39aac2f 100644 --- a/perl/t/vcfBlatAugment.t +++ b/perl/t/vcfBlatAugment.t @@ -36,11 +36,11 @@ const my @HEADER_ENDS => do { const my $HEADER_LINES => 3393; const my $DATA_ARR_D => [qw(chr10 11201 id CA C 390 . PC=D;RS=11201;RE=11205;LEN=1;S1=11;S2=849.236;REP=3 GT:PP:NP ./.:10:0)]; -const my $RES_ARR_D => [13, 3, 0.016, 9, 3, 0.007, 0.429]; +const my $RES_ARR_D => [12, 3, 0.015, 8, 3, 0.006, 0.423]; const my $DATA_ARR_DI => [qw(chr10 22777 id AGAAACTGTG ACTGTGAGATAGATATATATAGATAGATATAT 105 . PC=DI;RS=22777;RE=22787;LEN=9;S1=6;REP=0 GT:PP:NP ./.:0:5)]; -const my $RES_ARR_DI => [2, 3, 0.001, 2, 8, 0.017, 0.667]; +const my $RES_ARR_DI => [2, 2, 0.002, 2, 8, 0.017, 0.714]; const my $DATA_ARR_SI => [qw(chr10 11643 id C CG 150 . PC=I;RS=11643;RE=11649;LEN=1;S1=6;S2=421.908;REP=4 GT:PP:NP ./.:0:5)]; -const my $RES_ARR_SI => [7, 14, 0.014, 7, 10, 0.005, 0.447]; +const my $RES_ARR_SI => [7, 10, 0.014, 7, 10, 0.005, '0.500']; my ($stdout_fh, $buffer); my ($sam_stdout_fh, $sam_buffer); From 50eaaee25697614dd451daf75437e859e51b2a8e Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Thu, 14 May 2020 12:10:06 +0000 Subject: [PATCH 10/60] Final version of plots --- python/pindelMmPlots.py | 103 +++++++++++++++++++++++++--------------- 1 file changed, 65 insertions(+), 38 deletions(-) diff --git a/python/pindelMmPlots.py b/python/pindelMmPlots.py index 8882e24..fbce4ae 100755 --- a/python/pindelMmPlots.py +++ b/python/pindelMmPlots.py @@ -9,37 +9,81 @@ import vcfpy import pandas as pd import seaborn as sns +import matplotlib.pyplot as plt -def parse_vcf(df_list, rl, vcfin): +def parse_vcf(df_list, vaf_counts, rl, vcfin, type): reader = vcfpy.Reader.from_path(vcfin) sample = reader.header.samples.names[0] for record in reader: - #count +=1 - #if count > 1000: - # break - if record.INFO['PC'] != args.type: + if record.INFO['PC'] != type: continue call = record.call_for_sample[sample] if call.data.get('MTP') is None and call.data.get('MTN'): continue - df_list.append({'CHROM': record.CHROM, 'RL': rl, 'DT': 'WT', 'Mismatch': call.data.get('WTM') or 0, 'POS_READS': call.data.get('WTP'), 'NEG_READS': call.data.get('WTN')}) - df_list.append({'CHROM': record.CHROM, 'RL': rl, 'DT': 'MT', 'Mismatch': call.data.get('MTM') or 0, 'POS_READS': call.data.get('MTP'), 'NEG_READS': call.data.get('MTN')}) - if call.data.get('WTP') > 300: - print(f'{record.CHROM}:{record.POS}..{record.POS}\t{record.CHROM}:{record.POS}-{record.POS}') + # mismatch data only on Pos + # diff data only on MT + df_list.append({'RL': rl, 'SAMPLE': 'MT', 'STRAND': 'POS', 'Reads': call.data.get('MTP'), 'Mismatch': call.data.get('WTM') or 0, 'Diff': call.data.get('MTP') - call.data.get('PP')}) + df_list.append({'RL': rl, 'SAMPLE': 'MT', 'STRAND': 'NEG', 'Reads': call.data.get('MTN'), 'Diff': call.data.get('MTN') - call.data.get('NP')}) + if(call.data.get('VAF') == 0.000): + if rl in vaf_counts: + vaf_counts[float(rl)] += 1 + else: + vaf_counts[float(rl)] = 1 + df_list.append({'RL': rl, 'SAMPLE': 'WT', 'STRAND': 'POS', 'Reads': call.data.get('WTP'), 'Mismatch': call.data.get('MTM') or 0}) + df_list.append({'RL': rl, 'SAMPLE': 'WT', 'STRAND': 'NEG', 'Reads': call.data.get('WTN')}) -def draw_boxplot(df_list: list, x_item: str, y_item: str, hue: str, out_file: str, ylim=None): - plot = sns.boxplot( - x=x_item, y=y_item, - data=pd.DataFrame.from_records(df_list), - palette="colorblind", - hue=hue, +def process_vcfs(options): + ### split the labels and create map with vcfs + r_lengths = options.labels.split(',') + if len(r_lengths) != len(options.vcfs): + sys.exit('Error: "-labels" needs to have the same number of elements as the number of vcfs supplied.') + vcfs = {} + for l, v in zip(r_lengths, options.vcfs): + vcfs[float(l)] = v + + df_list = [] + vaf_counts = {} + for vcf_set in vcfs.items(): + print(f'Processing read length multiplier {vcf_set[0]} for type {options.type}') + parse_vcf(df_list, vaf_counts, vcf_set[0], vcf_set[1], options.type) + df = pd.DataFrame.from_records(df_list) + + facet_boxplot(df, 'SAMPLE', 'STRAND', 'RL', 'Reads', options.type+'_reads.png', title='BLAT read depth', ylim=(0, 50)) + facet_boxplot(df, 'SAMPLE', None, 'RL', 'Mismatch', options.type+'_mm.png', title='Mismatch fraction for BLAT reads', aspect=1.2, ylim=(0, 0.05)) + facet_boxplot(df, 'STRAND', None, 'RL', 'Diff', options.type+'_diff.png', title='BLAT reads - Pindel reads', aspect=1.2, ylim=(-20, 10)) + barchart(vaf_counts, 'RL', '0-VAF', options.type+'_0vaf.png', title='Events with VAF=0') + +def facet_boxplot(df, row: str, col: str, x_item: str, y_item: str, out_file: str, title=None, aspect=1, ylim=None): + sns.set() + grid = sns.FacetGrid( + df, + row=row, col=col, margin_titles=True, + ylim=ylim, aspect=aspect ) - if ylim: - plot.set(ylim=ylim) - fig = plot.get_figure() - fig.savefig(out_file) - fig.clf() # this clears the figure + grid.map(sns.boxplot, x_item, y_item); + if title: + grid.fig.subplots_adjust(top=.9) + grid.fig.suptitle(title, size=14) + grid.set_xticklabels(rotation=80) + grid.savefig(out_file) + grid.fig.clf() # this clears the figure + +def barchart(data_dict, x_label: str, y_label: str, out_file: str, title=None): + sns.set() + x_items = [] + y_items = [] + for k in sorted(data_dict): + x_items.append(k) + y_items.append(data_dict[k]) + bp = sns.barplot(x=x_items, y=y_items) + if title: + bp.set_title(title) + bp.set_yscale('log') + bp.set_xlabel(x_label) + bp.set_ylabel(y_label) + bp.set_xticklabels(bp.get_xticklabels(), rotation=80) + plt.savefig(out_file) parser = argparse.ArgumentParser(description='Generate wisker plots of mismatch rates for a set of vcfs') @@ -55,21 +99,4 @@ def draw_boxplot(df_list: list, x_item: str, y_item: str, hue: str, out_file: st if os.path.exists(args.outDir) is False: os.mkdir(args.outDir, mode=0o700) # rwx owner only -### split the labels and create map with vcfs -r_lengths = args.labels.split(',') -if len(r_lengths) != len(args.vcfs): - sys.exit('Error: "-labels" needs to have the same number of elements as the number of vcfs supplied.') -vcfs = {} -for l, v in zip(r_lengths, args.vcfs): - vcfs[float(l)] = v - -df_list = [] -for vcf_set in vcfs.items(): - print(f'Processing read length multiplier {vcf_set[0]} for type {args.type}') - parse_vcf(df_list, vcf_set[0], vcf_set[1]) - -draw_boxplot(df_list, 'RL', 'Mismatch', 'DT', args.type+'_mm.png') #, ylim=(0, 0.05)) -draw_boxplot(df_list, 'RL', 'POS_READS', 'DT', args.type+'_pos.png') #, ylim=(0, 0.05)) -draw_boxplot(df_list, 'RL', 'NEG_READS', 'DT', args.type+'_neg.png') #, ylim=(0, 0.05)) - - +process_vcfs(args) From 7302b1dfb1a2193a729d1616ada3983f56f21062 Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Tue, 2 Jun 2020 10:21:39 +0000 Subject: [PATCH 11/60] Fix bug in read selection, performance implications but best I can think of --- .../Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm index 879609c..0787eee 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm @@ -272,24 +272,13 @@ sub blat_record { sub read_ranges { my ($self, $v_h) = @_; # return a string of chr:s-e... if approprate. - my $ret_val; - my ($chr, $q_start, $q_end) = ($v_h->{CHROM}, $v_h->{q_start}, $v_h->{q_end}); my $read_buffer = $self->{max_insert}; - if($q_start + ($read_buffer * 2) > $q_end) { - $ret_val = sprintf $LOCI_FMT, $chr, $q_start - $read_buffer, $q_end + $read_buffer; - } - else { - $ret_val = sprintf $LOCI_FMT, $chr, $q_start - $read_buffer, $q_start + $read_buffer; - $ret_val .= q{ }; - $ret_val .= sprintf $LOCI_FMT, $chr, $q_end - $read_buffer, $q_end + $read_buffer - } -#print "$ret_val\n"; - return $ret_val; + return sprintf $LOCI_FMT, $v_h->{CHROM}, $v_h->{q_start} - $read_buffer, $v_h->{q_end} + $read_buffer; } sub blat_reads { my ($self, $v_h, $file_target) = @_; - # setup ther temp files + # setup the temp files my ($fh_query, $file_query) = tempfile( SUFFIX => '.fa', UNLINK => 1); close $fh_query or die "Failed to close $file_query (query reads)"; my ($fh_psl, $file_psl) = tempfile( SUFFIX => '.psl', UNLINK => 1); From 9fa478b8fe3e1182e4fb8c6cffddf136d1acbf22 Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Tue, 2 Jun 2020 10:21:55 +0000 Subject: [PATCH 12/60] cleanup --- perl/lib/Sanger/CGP/Pindel/OutputGen/VcfCohortConverter.pm | 5 ----- 1 file changed, 5 deletions(-) diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfCohortConverter.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfCohortConverter.pm index 7c8af01..0732e72 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfCohortConverter.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfCohortConverter.pm @@ -155,11 +155,6 @@ sub gen_header{ {key => 'FORMAT', ID => 'S2', Number => 1, Type => 'Float', Description => 'Pindel S2 score, not present for all types'}, {key => 'FORMAT', ID => 'PP', Number => 1, Type => 'Integer', Description => 'Pindel calls on the positive strand'}, {key => 'FORMAT', ID => 'NP', Number => 1, Type => 'Integer', Description => 'Pindel calls on the negative strand'}, - #{key => 'FORMAT', ID => 'WTP', Number => 1, Type => 'Integer', Description => '+ve strand reads BLATed (or count for large deletions) to reference at this location'}, - #{key => 'FORMAT', ID => 'WTN', Number => 1, Type => 'Integer', Description => '-ve strand reads BLATed (or count for large deletions) to reference at this location'}, - #{key => 'FORMAT', ID => 'MTP', Number => 1, Type => 'Integer', Description => '+ve strand reads BLATed to alternate sequence at this location'}, - #{key => 'FORMAT', ID => 'MTN', Number => 1, Type => 'Integer', Description => '-ve strand reads BLATed to alternate sequence at this location'}, - #{key => 'FORMAT', ID => 'VAF', Number => 1, Type => 'Float', Description => 'Variant allele fraction using reads that unabiguously map to ref or alt seq (to 3 d.p.)'}, ); my @blank_fmt = (q{.}) x (scalar @format -1); From 477a0e633db50d548d091d57b67cc3fe14b85532 Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Tue, 2 Jun 2020 10:22:12 +0000 Subject: [PATCH 13/60] lazy stuff --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 4203ca7..6a8bcf5 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ /perl/pm_to_blib .idea/* /python/env +/tmp From 8f9d163d4b126b74365bbe7a019096f1157259c7 Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Tue, 23 Jun 2020 14:16:45 +0000 Subject: [PATCH 14/60] convert to have ability to work on multisample VCF --- .dockerignore | 2 + perl/bin/pindel_blat_vaf.pl | 39 ++-- .../CGP/Pindel/OutputGen/VcfBlatAugment.pm | 185 +++++++++++------- perl/t/vcfBlatAugment.t | 23 ++- 4 files changed, 152 insertions(+), 97 deletions(-) diff --git a/.dockerignore b/.dockerignore index aa69bb0..613cb84 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,5 +6,7 @@ /CHANGES.md /.gitignore /.git +/perl/blib +/pm_to_blib /perl/docs /perl/docs.tar.gz diff --git a/perl/bin/pindel_blat_vaf.pl b/perl/bin/pindel_blat_vaf.pl index 5f92419..bc772c1 100755 --- a/perl/bin/pindel_blat_vaf.pl +++ b/perl/bin/pindel_blat_vaf.pl @@ -20,8 +20,9 @@ ofh => $options->{output}, sam => $options->{align}, hts_file => $options->{hts}, - pad_mult => $options->{pad}, + outpath => $options->{outpath}, ); + $augment->output_header; $augment->process_records; } @@ -30,19 +31,22 @@ sub setup{ my %opts = ( 'cmd' => join(" ", $0, @ARGV), + 'hts' => [], ); + my @hts_files; GetOptions( 'h|help' => \$opts{h}, 'm|man' => \$opts{m}, 'v|version' => \$opts{v}, 'o|output=s' => \$opts{output}, - 'a|align=s' => \$opts{align}, 'r|ref=s' => \$opts{ref}, 'i|input=s' => \$opts{input}, - 'p|pad:f' => \$opts{pad}, 'd|debug' => \$opts{debug}, - 'hts=s' => \$opts{hts}, + 'hts=s@' => \@hts_files, ); + $opts{hts} = [split(/,/,join(',',@hts_files))]; + + if(defined $opts{'v'}) { printf "Version: %s\n", Sanger::CGP::Pindel::Implement->VERSION; exit; @@ -53,20 +57,21 @@ sub setup{ PCAP::Cli::file_for_reading('ref', $opts{ref}); PCAP::Cli::file_for_reading('input', $opts{input}); - PCAP::Cli::file_for_reading('hts', $opts{hts}); - unless(-e $opts{hts}.'.bai' || -e $opts{hts}.'.csi' || -e $opts{hts}.'.cram') { - die "ERROR: Unable to find appropriate index file for $opts{hts}\n"; - } - unless(-e $opts{hts}.'.bas') { - die "ERROR: Unable to find *.bas file for $opts{hts}\n"; + for my $t(@{$opts{hts}}) { + PCAP::Cli::file_for_reading('hts', $t); + unless(-e $t.'.bai' || -e $t.'.csi' || -e $t.'.crai') { + die "ERROR: Unable to find appropriate index file for $t\n"; + } + unless(-e $t.'.bas') { + die "ERROR: Unable to find *.bas file for $t\n"; + } } $opts{align} = $opts{output}.'.sam' unless(defined $opts{align}); + $opts{outpath} = $opts{output}; open my $ofh, '>', $opts{output}; $opts{output} = $ofh; - open my $sfh, '>', $opts{align}; - $opts{align} = $sfh; return \%opts; } @@ -79,9 +84,7 @@ =head1 NAME =head1 SYNOPSIS -pindel_blat_vaf.pl [options] SAMPLE.bam - - SAMPLE.bam should have co-located *.bai and *.bas files. +pindel_blat_vaf.pl [options] Required parameters: -ref -r File path to the reference file used to provide the coordinate system. @@ -89,10 +92,6 @@ =head1 SYNOPSIS -hts BAM/CRAM file for associated sample. -output -o File path for VCF output (not compressed) - Optional parameters: - -align -a File path for accepted alignments (SAM records, no header) [defaults to -o .sam] - -pad -p Multiples (>=1) of max readlength to pad blat target seq with [default 1] - Other: -help -h Brief help message. -man -m Full documentation. @@ -100,7 +99,7 @@ =head1 SYNOPSIS =head1 DESCRIPTION -B will attempt to generate a vcf with expanded counts and VAR. +B will attempt to generate a vcf with expanded counts and VAF. For every variant called by Pindel a blat will be performed and the results merged into a single vcf record. diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm index 0787eee..369f7aa 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm @@ -42,12 +42,11 @@ use Data::Dumper; const my $SD_MULT => 2; const my $V_FMT => 8; -const my $V_GT => 9; +const my $V_GT_START => 9; const my $READS_AND_BLAT => q{bash -c 'set -o pipefail ; samtools view -uF 3840 %s %s | samtools fasta - > %s && blat -t=dna -q=dna -noTrimA -minIdentity=95 -noHead -out=psl %s %s %s && pslPretty -long -axt %s %s %s /dev/stdout'}; # returns +ve and then -ve results const my $SAM_DEPTH_PN => q{bash -c "set -o pipefail ; samtools view -uF 3844 %s %s | pee 'samtools view -c -F 16 -' 'samtools view -c -f 16 -'"}; const my $LOCI_FMT => '%s:%d-%d'; -const my $TARGET_PAD_MULTIPLIER => 1; const my $PAD_EVENT => 3; const my $MAPPED_RL_MULT => 0.6; @@ -62,93 +61,134 @@ sub new{ input => $args{input}, ref => $args{ref}, ofh => $args{ofh}, # vcf - sfh => $args{sam}, # sam reads hts_file => $args{hts_file}, - target_pad_multiplier => $TARGET_PAD_MULTIPLIER, + fill_in => $args{fill_in}, }; bless $self, $class; - if(exists $args{pad_mult} && defined $args{pad_mult}) { - $self->{target_pad_multiplier} = $args{pad_mult} + 0; - } - - $self->_init; + $self->_init($args{outpath}); return $self; } sub _init { - my $self = shift; + my ($self, $outpath) = @_; my $vcf = Vcf->new(file => $self->{input}); - $vcf->parse_header(); + $vcf->parse_header; $self->{vcf} = $vcf; - $self->_add_headers; - $self->_hts; - my $bas_sample = $self->_buffer_sizes; # has to go before validate sample - $self->_validate_sample($bas_sample); + $self->_sample_order; # needs vcf + $self->_align_output($outpath); + $self->_add_headers unless($self->{fill_in}); + $self->_hts; # has to go before _buffer_sizes + $self->_buffer_sizes; # has to go before _validate_sample + $self->_validate_samples; # load the fai $self->{fai} = Bio::DB::HTS::Faidx->new($self->{ref}); return 1; } -sub _validate_sample { - my ($self, $bas_sample) = @_; - # check BAM/CRAM and VCF have same sample +sub _close_sams { + my $self = shift; + for my $sample(@{$self->{vcf_sample_order}}) { + close $self->{sfh}->{$sample}; + } + return 1; +} + +sub _align_output { + my ($self, $outpath) = @_; + $outpath =~ s/\.vcf$//; + for my $sample(@{$self->{vcf_sample_order}}) { + my $sam_file = sprintf '%s.%s.sam', $outpath, $sample; + unlink $sam_file if(-e $sam_file); + open my $SAM, '>', $sam_file; + $self->{sfh}->{$sample} = $SAM; + } + return 1; +} +sub _sample_order { + my $self = shift; + my $i = 9; # genotype sample col from 9 + my %samp_pos; + my @ordered_samples = $self->{vcf}->get_samples; + for my $s(@ordered_samples) { + $samp_pos{$s} = $i++; + } + $self->{vcf_sample_pos} = \%samp_pos; + $self->{vcf_sample_order} = \@ordered_samples; + return 1; +} + +sub _sample_from_hts { + my ($self, $hts) = @_; my $hts_sample; - foreach my $line (split(/\n/,$self->{hts}->header->text)) { + foreach my $line (split(/\n/,$hts->header->text)) { next unless($line =~ m/^\@RG/); chomp $line; ($hts_sample) = $line =~ m/SM:([^\t]+)/; last if(defined $hts_sample); } + die sprintf "ERROR: Failed to find a SM tag in a readgroup header of %s\n", $hts->hts_path unless(defined $hts_sample); + return $hts_sample; +} + +sub _validate_samples { + my $self = shift; + # check BAM/CRAM and VCF have same sample my @samples = $self->{vcf}->get_samples(); - die sprintf "ERROR: Only expecting 1 sample in VCF '%s', got %d\n", $self->{vcf}, scalar @samples if(@samples > 1); - die sprintf "ERROR: Sample mismatch between BAM/CRAM (%s) and VCF (%s)\n", $hts_sample, $samples[0] if($hts_sample ne $samples[0]); - die sprintf "ERROR: Sample mismatch between BAM/CRAM (%s) and BAS (%s)\n", $hts_sample, $bas_sample if($hts_sample ne $bas_sample); - $self->{sample} = $hts_sample; + for my $vcf_s(sort @samples) { + next if(exists $self->{hts}->{$vcf_s}); + die sprintf "ERROR: Sample '%s' is not represented in the BAM/CRAM files provided.\n", $vcf_s; + } + return 1; } sub _buffer_sizes { my $self = shift; - ## Set the max read length and insert size + SD*$SD_MULTI using the bas file data - my $b = PCAP::Bam::Bas->new($self->{hts_file}.'.bas'); my $max_ins = 0; my $max_rl = 0; my $min_rl = 1_000_000; - my $sample; - for my $rg($b->read_groups) { - my $m_sd = int ($b->get($rg, 'mean_insert_size') + ($b->get($rg, 'insert_size_sd') * $SD_MULT)); - $max_ins = $m_sd if($m_sd > $max_ins); - my $tmp_max = max ($b->get($rg, 'read_length_r1'), $b->get($rg, 'read_length_r2')); - my $tmp_min = min ($b->get($rg, 'read_length_r1'), $b->get($rg, 'read_length_r2')); - $min_rl = $tmp_min if($tmp_min < $min_rl); - $max_rl = $tmp_max if($tmp_max > $max_rl); - my $s = $b->get($rg, 'sample'); - if($sample) { - die "ERROR: Multiple samples found in %s\n", $self->{hts}.'.bas' if($sample ne $s) - } - else { - $sample = $s; + for my $hts_sample(keys %{$self->{hts}}) { + my $b = PCAP::Bam::Bas->new($self->{hts}->{$hts_sample}->hts_path.'.bas'); + my $sample; + for my $rg($b->read_groups) { + my $m_sd = int ($b->get($rg, 'mean_insert_size') + ($b->get($rg, 'insert_size_sd') * $SD_MULT)); + $max_ins = $m_sd if($m_sd > $max_ins); + my $tmp_max = max ($b->get($rg, 'read_length_r1'), $b->get($rg, 'read_length_r2')); + my $tmp_min = min ($b->get($rg, 'read_length_r1'), $b->get($rg, 'read_length_r2')); + $min_rl = $tmp_min if($tmp_min < $min_rl); + $max_rl = $tmp_max if($tmp_max > $max_rl); + my $s = $b->get($rg, 'sample'); + if($sample) { + die "ERROR: Multiple samples found in %s.bas\n", $self->{hts}->{$hts_sample}->hts_path if($sample ne $s); + if($sample ne $hts_sample) { + die "ERROR: Sample in bas file (%s) doesn't match bam/cram file (%s), + %s vs %s.bas\n", $sample, $hts_sample, + $self->{hts}->{$hts_sample}->hts_path, $self->{hts}->{$hts_sample}->hts_path; + } + } + else { + $sample = $s; + } } } $self->{min_rl} = $min_rl; $self->{max_insert} = $max_ins; -#printf "Defined: %d\n", $max_rl + $max_ins; -#printf "Pad mult: %d\n", int($max_rl * $self->{target_pad_multiplier}); - if($self->{target_pad_multiplier} == 0) { - $self->{target_pad} = $max_rl + $max_ins; - } - else { - $self->{target_pad} = int($max_rl * $self->{target_pad_multiplier}); - } -#printf "target_pad: %d\n", $self->{target_pad}; - return $sample; + $self->{target_pad} = $max_rl; + return 1; } sub _hts { my $self = shift; - $self->{hts} = Bio::DB::HTS->new(-bam => $self->{hts_file}, -fasta => $self->{ref}); + for my $hts(@{$self->{'hts_file'}}) { + my $tmp = Bio::DB::HTS->new(-bam => $hts, -fasta => $self->{ref}); + my $sample = $self->_sample_from_hts($tmp); + if(exists $self->{hts}->{$sample}) { + die sprintf "ERROR: More than one BAM/CRAM file for sample %s, %s vs %s\n", $sample, $self->{hts}->{$sample}->hts_path, $hts->hts_path; + } + $self->{hts}->{$sample} = $tmp; + } return 1; } @@ -244,12 +284,17 @@ my $count=0; #$count++; #next if($count != 1); my $v_h = $self->to_data_hash($v_d); - my @extra_gt = $self->blat_record($v_h); - $v_d->[$V_FMT] .= $self->{fmt_ext}; - $v_d->[$V_GT] = join q{:}, $v_d->[$V_GT], @extra_gt; + my $extra_gt = $self->blat_record($v_h); + $v_d->[$V_FMT] .= $self->{fmt_ext} unless($self->{fill_in}); + my $gt_pos = $V_GT_START; + for my $gt_set (@{$extra_gt}) { + $v_d->[$gt_pos] = join q{:}, $v_d->[$gt_pos], @{$gt_set}; + $gt_pos++; + } printf $fh "%s\n", join "\t", @{$v_d}; #last; } + $self->_close_sams; } sub blat_record { @@ -266,25 +311,29 @@ sub blat_record { $v_h->{change_pos_low} = $change_pos_low; $v_h->{change_pos_high} = $change_pos_high; - return $self->blat_reads($v_h, $file_target); + my @blat_sets; + for my $sample(@{$self->{vcf_sample_order}}) { + push @blat_sets, $self->blat_reads($v_h, $file_target, $sample); + } + return \@blat_sets; } sub read_ranges { - my ($self, $v_h) = @_; + my ($self, $v_h, $sample) = @_; # return a string of chr:s-e... if approprate. my $read_buffer = $self->{max_insert}; return sprintf $LOCI_FMT, $v_h->{CHROM}, $v_h->{q_start} - $read_buffer, $v_h->{q_end} + $read_buffer; } sub blat_reads { - my ($self, $v_h, $file_target) = @_; + my ($self, $v_h, $file_target, $sample) = @_; # setup the temp files my ($fh_query, $file_query) = tempfile( SUFFIX => '.fa', UNLINK => 1); close $fh_query or die "Failed to close $file_query (query reads)"; my ($fh_psl, $file_psl) = tempfile( SUFFIX => '.psl', UNLINK => 1); close $fh_psl or die "Failed to close $file_psl (psl output)"; - my $c_blat = sprintf $READS_AND_BLAT, $self->{hts_file}, $self->read_ranges($v_h), $file_query, $file_target, $file_query, $file_psl, $file_psl, $file_target, $file_query; + my $c_blat = sprintf $READS_AND_BLAT, $self->{hts}->{$sample}->hts_path, $self->read_ranges($v_h, $sample), $file_query, $file_target, $file_query, $file_psl, $file_psl, $file_target, $file_query; my ($c_out, $c_err, $c_exit) = capture { system($c_blat); }; if($c_exit) { warn "An error occurred while executing $c_blat\n"; @@ -299,11 +348,11 @@ sub blat_reads { #print "$c_blat\n"; #print "$c_out\n"; - my ($wtp, $wtn, $mtp, $mtn, $wt_bmm, $mt_bmm) = $self->psl_axt_parser(\$c_out, $v_h); + my ($wtp, $wtn, $mtp, $mtn, $wt_bmm, $mt_bmm) = $self->psl_axt_parser(\$c_out, $v_h, $sample); my ($wtm, $mtm) = (q{.}, q{.}); my $wtr = $wtp + $wtn; if($wtp + $wtn == 0) { - ($wtp, $wtn) = $self->sam_depth($v_h); + ($wtp, $wtn) = $self->sam_depth($v_h, $sample); $wtr = $wtp + $wtn; } else { @@ -315,11 +364,11 @@ sub blat_reads { } my $depth = $wtr+$mtr; my $vaf = $depth ? sprintf("%.3f", $mtr/$depth) : 0; - return ($wtp, $wtn, $wtm, $mtp, $mtn, $mtm, $vaf); + return [$wtp, $wtn, $wtm, $mtp, $mtn, $mtm, $vaf]; } sub psl_axt_parser { - my ($self, $blat_axt, $v_h) = @_; + my ($self, $blat_axt, $v_h, $sample) = @_; # collate the data by readname and order by score my @lines = split /\n/, ${$blat_axt}; my $line_c = @lines; @@ -352,7 +401,7 @@ my $SKIP_EVENT = q{}; next READ; } my $record = $records[0]; - if($self->parse_axt_event($v_h, $record) == 1) { + if($self->parse_axt_event($v_h, $record, $sample) == 1) { $type_strand{$record->[0].$record->[6]} += 1; $bmm_sums{$record->[0]} += bmm($record->[8], $record->[9]); } @@ -381,7 +430,7 @@ sub bmm { } sub parse_axt_event { - my ($self, $v_h, $rec) = @_; + my ($self, $v_h, $rec, $sample) = @_; my ($t_name, $t_start, $t_end, $q_name, $q_start, $q_end, $strand, $score, $t_seq, $q_seq) = @{$rec}; # specific to deletion class @@ -423,7 +472,7 @@ sub parse_axt_event { && substr($change_seq,-1,1) eq substr($sub_q_seq,-1,1) # matching last base ) { $retval = 1; - $self->sam_record($v_h, $rec); + $self->sam_record($v_h, $rec, $sample); } } } @@ -432,7 +481,7 @@ sub parse_axt_event { } sub sam_record { - my($self, $v_h, $rec) = @_; + my($self, $v_h, $rec, $sample) = @_; # warn Dumper($v_h); # warn Dumper($rec); @@ -467,16 +516,16 @@ sub sam_record { #print $cigar."\n"; } #printf "%s:%d-%d\n", $v_h->{CHROM}, $pos, $pos + 100; - printf {$self->{sfh}} "%s\n", join "\t", $qname, $flag, $v_h->{CHROM}, $pos, 60, $cigar, '*', 0, 0, $seq, '*'; + printf {$self->{sfh}->{$sample}} "%s\n", join "\t", $qname, $flag, $v_h->{CHROM}, $pos, 60, $cigar, '*', 0, 0, $seq, '*'; # ; } sub sam_depth { - my ($self, $v_h) = @_; + my ($self, $v_h, $sample) = @_; my $mid_point = int ($v_h->{RS} + (($v_h->{RE} - $v_h->{RS})*0.5)); my $read_search = sprintf $LOCI_FMT, $v_h->{CHROM}, $mid_point, $mid_point; - my $c_samcount = sprintf $SAM_DEPTH_PN, $self->{hts_file}, $read_search; + my $c_samcount = sprintf $SAM_DEPTH_PN, $self->{hts}->{$sample}->hts_path, $read_search; my ($c_out, $c_err, $c_exit) = capture { system($c_samcount); }; if($c_exit) { warn "An error occurred while executing $c_samcount\n"; diff --git a/perl/t/vcfBlatAugment.t b/perl/t/vcfBlatAugment.t index 39aac2f..413a960 100644 --- a/perl/t/vcfBlatAugment.t +++ b/perl/t/vcfBlatAugment.t @@ -61,22 +61,22 @@ subtest 'Header checks' => sub { subtest 'Simple Deletion checks' => sub { my $vba = new_vba(catfile($DATA, 'D.vcf')); my $v_h = $vba->to_data_hash($DATA_ARR_D); - my @gt_out = $vba->blat_record($v_h); - is_deeply(\@gt_out, $RES_ARR_D); + my $gt_out = $vba->blat_record($v_h); + is_deeply($gt_out->[0], $RES_ARR_D); }; subtest 'Simple Insertion checks' => sub { my $vba = new_vba(catfile($DATA, 'SI.vcf')); my $v_h = $vba->to_data_hash($DATA_ARR_SI); - my @gt_out = $vba->blat_record($v_h); - is_deeply(\@gt_out, $RES_ARR_SI); + my $gt_out = $vba->blat_record($v_h); + is_deeply($gt_out->[0], $RES_ARR_SI); }; subtest 'Complex event checks' => sub { my $vba = new_vba(catfile($DATA, 'DI.vcf')); my $v_h = $vba->to_data_hash($DATA_ARR_DI); - my @gt_out = $vba->blat_record($v_h); - is_deeply(\@gt_out, $RES_ARR_DI); + my $gt_out = $vba->blat_record($v_h); + is_deeply($gt_out->[0], $RES_ARR_DI); }; done_testing(); @@ -84,13 +84,18 @@ done_testing(); sub new_vba { my $vcf = shift; - return new_ok($MODULE, [ + my $tmp = '/tmp/pindel_test_stuff'; + my $obj = new_ok($MODULE, [ input => $vcf, ref => catfile($DATA, 'chr10_1-23700.fa'), ofh => buffer_fh(), - sam => sam_buffer_fh(), - hts_file => catfile($DATA, 'test.bam'), + outpath => $tmp, + hts_file => [catfile($DATA, 'test.bam')], ]); + my $sample = $obj->{vcf_sample_order}->[0]; + $obj->{sfh}->{$sample} = sam_buffer_fh(); + unlink $tmp; + return $obj; } sub buffer_fh { From e166a670bfb31351eeabcdf1c1c9eb1cc25c0e7e Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Tue, 28 Jul 2020 12:50:43 +0000 Subject: [PATCH 15/60] working towards vaf fill in --- perl/bin/pindelCohortMerge.pl | 248 ++++++++++++++++++ perl/bin/pindelCohortVafFill.pl | 75 ++++++ perl/bin/pindelCohortVafSplit.pl | 124 +++++++++ .../CGP/Pindel/OutputGen/VcfBlatAugment.pm | 7 +- perl/util/README.md | 3 + perl/util/pairedSplit.pl | 66 +++++ 6 files changed, 522 insertions(+), 1 deletion(-) create mode 100644 perl/bin/pindelCohortMerge.pl create mode 100644 perl/bin/pindelCohortVafFill.pl create mode 100755 perl/bin/pindelCohortVafSplit.pl create mode 100644 perl/util/README.md create mode 100644 perl/util/pairedSplit.pl diff --git a/perl/bin/pindelCohortMerge.pl b/perl/bin/pindelCohortMerge.pl new file mode 100644 index 0000000..8e51928 --- /dev/null +++ b/perl/bin/pindelCohortMerge.pl @@ -0,0 +1,248 @@ +#!/usr/bin/env perl + +use strict; +use warnings FATAL => 'all'; +use autodie qw(:all); +use Cwd qw(abs_path); +use Pod::Usage qw(pod2usage); +use FindBin qw($Bin); +use lib "$Bin/../lib"; +use Getopt::Long; +use Capture::Tiny qw(capture); +use IO::Uncompress::Gunzip qw(gunzip $GunzipError); +use Data::UUID; +use Set::IntervalTree; + +=head +# Build the new header +zgrep -B 1000000 -m 1 '^#CHROM' old_vcfs/PD37237b.pindel.vcf.gz | head -n -1 > new_header +zgrep -hm 1 '^##SAMPLE' old_vcfs/PD37237b*.pindel.vcf.gz >> new_header +zgrep -m 1 '^#CHROM' old_vcfs/PD37237b.pindel.vcf.gz >> new_header +=cut + +my $options = setup(); +my ($vcf_by_sample, $sample_head) = vcf_by_samples($options->{vcfs}); +my ($records, $sample_order) = collate_data($vcf_by_sample); + +# write stuff +header($options->{output}, $options->{vcfs}, $sample_head); +records($options->{output}, $records, $sample_order, $options->{all}, $options->{np}, $options->{control}); + +sub records { + my ($output, $records, $sample_order, $all, $np_tree, $control) = @_; + my %ds = %{$records}; + my $uuid_gen = Data::UUID->new; + my @samples = @{$sample_order}; + for my $chr(sort keys %ds) { + my $chr_tree; + if(defined $np_tree && exists $np_tree->{$chr}) { + $chr_tree = $np_tree->{$chr}; + } + else { + $chr_tree = Set::IntervalTree->new(); + } + for my $pos(sort {$a <=> $b} keys %{$ds{$chr}}) { + for my $seq_key(sort keys %{$ds{$chr}{$pos}}) { + next if($control && exists $ds{$chr}{$pos}{$seq_key}{$control}); + + my ($info, $format) = @{$ds{$chr}{$pos}{$seq_key}{_DATA_}}; + + if(defined $np_tree ) { + $_ = q{;}.$info; + my ($rs) = $_ =~ m/;RS=(\d+)/; + my ($re) = $_ =~ m/;RE=(\d+)/; + next if(@{$chr_tree->fetch($rs, $re)} > 0); + } + + my ($ref, $alt) = split ':', $seq_key; + my $row = join "\t", $chr, $pos, $uuid_gen->to_string($uuid_gen->create), $ref, $alt, q{.}, q{}, $info, $format; + my $samples_with_vaf = 0; + for my $s(@samples) { + if(exists $ds{$chr}{$pos}{$seq_key}{$s}) { + $row .= "\t".$ds{$chr}{$pos}{$seq_key}{$s}; + $samples_with_vaf++ if($row !~ m/:0\.000$/); + } + else { + $row .= "\t."; + } + } + if($all || $samples_with_vaf > 0) { + print $output $row."\n"; + } + } + } + } + #printf "Loci requiring fill-in: %d\n", $fill_in_loci; + #printf "Fill in required for: %d\n", $to_fill_in; + +} + +sub collate_data { + my ($vcf_by_sample) = @_; + my %ds; + my @samples = sort keys %{$vcf_by_sample}; + for my $s(@samples) { + my $fh = IO::Uncompress::Gunzip->new($vcf_by_sample->{$s}, MultiStream => 1, AutoClose=> 1) or die "gunzip failed: $GunzipError\n";; + while (<$fh>) { + next if(m/^#/); + chomp; + my ($chr, $pos, undef, $ref, $alt, undef, undef, $info, $format, $data) = split /\t/, $_; + my $seq_key = sprintf '%s:%s', $ref, $alt; + if(! exists $ds{$chr}{$pos}{$seq_key}) { + $ds{$chr}{$pos}{$seq_key}{_DATA_} = [$info, $format]; + } + $ds{$chr}{$pos}{$seq_key}{$s} = $data; + } + } + return (\%ds, \@samples); +} + + + + +sub header { + my ($output, $vcfs, $sample_head) = @_; + my $metadata_header = _command_output(sprintf q{zgrep -B 1000000 -m 1 '^#CHROM' %s | head -n -1}, $vcfs->[0]); + my $col_header = _command_output(sprintf q{zgrep -m 1 '^#CHROM' %s}, $vcfs->[0]); + # remove last col as has sample will be added back + ${$col_header} =~ s/\t[^\t]+$//; + my @s_keys = sort keys %{$sample_head}; + + print $output ${$metadata_header}."\n"; + for (@s_keys) { + print $output ${$sample_head->{$_}}."\n"; + } + print $output join "\t", ${$col_header}, @s_keys; + print $output "\n"; + + return 1; +} + +sub vcf_by_samples { + my ($vcfs) = @_; + my %vcfs; + my %samps; + for my $v(@{$vcfs}) { + my $samp_head = _command_output(sprintf q{zgrep -hm 1 '^##SAMPLE' %s}, $v); + my ($sample) = ${$samp_head} =~ m/ID=([^,]+)/; + $vcfs{$sample} = $v; + $samps{$sample} = $samp_head; + } + return (\%vcfs, \%samps); +} + +sub _command_output { + my $command = shift; + my ($c_out, $c_err, $c_exit) = capture { system($command); }; + if($c_exit) { + warn "An error occurred while executing $command\n"; + warn "\tERROR$c_err\n"; + exit $c_exit; + } + chomp $c_out; + return \$c_out; +} + +sub np_lookup { + my ($gff3, $min_samp) = @_; + my $interval_count = 0; + printf STDERR "Loading normal panel...\n"; + my %tree; + my $z = IO::Uncompress::Gunzip->new($gff3, MultiStream => 1, AutoClose=> 1) or die "gunzip failed: $GunzipError\n"; + my $value = 1; + while(my $line = <$z>) { + next if ($line =~ m/^#/); + chomp $line; + # simple hash look up as only check start coord. + my ($chr, $start, $info) = (split /\t/, $line)[0,3,7]; + my ($samp_count) = $info =~ m/^SAMPLE_COUNT=(\d+)/; + next if($samp_count < $min_samp); + + $tree{$chr} = Set::IntervalTree->new() unless(exists $tree{$chr}); + $tree{$chr}->insert(\$value, $start, $start+1); # as half-open (i.e. last value is 1 past end) + $interval_count++; + } + printf STDERR "\tdone, $interval_count intervals loaded.\n"; + return \%tree; +} + +sub setup{ + my %opts = ( + 'cmd' => join(" ", $0, @ARGV), + 'mnps' => 1, + ); + GetOptions( 'h|help' => \$opts{h}, + 'm|man' => \$opts{m}, + 'v|version' => \$opts{v}, + 'o|output=s' => \$opts{output}, + 'n|np=s' => \$opts{np}, + 's|mnps=i' => \$opts{'mnps'}, + 'a|all' => \$opts{all}, + 'd|debug' => \$opts{debug}, + 'c|control=s' => \$opts{control}, + ); + + if(defined $opts{'v'}) { + printf "Version: %s\n", Sanger::CGP::Pindel::Implement->VERSION; + exit; + } + + pod2usage(-verbose => 1) if(defined $opts{h}); + pod2usage(-verbose => 2) if(defined $opts{m}); + + my @vcfs = @ARGV; + $opts{vcfs} = \@vcfs; + + if(@vcfs < 2) { + print STDERR "ERROR: More than 1 VCF input is required\n"; + pod2usage(-verbose => 1); + } + + if(defined $opts{np}) { + $opts{np} = np_lookup($opts{np}, $opts{mnps}); + } + else { + delete $opts{np}; + } + + $opts{align} = $opts{output}.'.sam' unless(defined $opts{align}); + + open my $ofh, '>', $opts{output}; + $opts{output} = $ofh; + + return \%opts; +} + +__END__ + +=head1 NAME + +pindelCohortMerge.pl - Takes outputs from pindelCohort.pl and merges into a single file, filtering events. + +=head1 SYNOPSIS + +pindelCohortMerge.pl [options] A.vcf.gz B.vcf.gz [...] + + Required parameters: + -output -o File path for VCF output (not compressed) + + Optional parameters: + -all -a Keep all events, even if VAF == 0/. for all samples + -np -n Normal panel gff3 file - ommit if no filtering required. + -mnps -s Minimum normal panel samples required to exclude [default: >=1] + -control -c Exclude any events where this sample has calls. + + Other: + -help -h Brief help message. + -man -m Full documentation. + -version -v Prints the version number. + +=head1 DESCRIPTION + +B generate a vcf with collated samples. + +For each common loci/REF/ALT merge the samples. + +Not vcf-merge (from vcftools) as that fails to retain GT ordering, even between rows. + +=cut diff --git a/perl/bin/pindelCohortVafFill.pl b/perl/bin/pindelCohortVafFill.pl new file mode 100644 index 0000000..23243c2 --- /dev/null +++ b/perl/bin/pindelCohortVafFill.pl @@ -0,0 +1,75 @@ +#!/usr/bin/env perl + +use strict; +use warnings FATAL => 'all'; +use autodie qw(:all); +use Cwd qw(abs_path); +use Pod::Usage qw(pod2usage); +use FindBin qw($Bin); +use lib "$Bin/../lib"; +use Getopt::Long; + +{ + my $options = setup(); +} + +sub setup { + my %opts = ( + suffix => '.vaf', + ); + GetOptions( 'h|help' => \$opts{h}, + 'm|man' => \$opts{'m'}, + 'v|version' => \$opts{v}, + 'i|input=s' => \$opts{input}, + 's|suffix:s' => \$opts{suffix}, + ); + + if(defined $opts{v}) { + printf "Version: %s\n", Sanger::CGP::Pindel::Implement->VERSION; + exit; + } + pod2usage(-verbose => 1) if(defined $opts{h}); + pod2usage(-verbose => 2) if(defined $opts{'m'}); + + PCAP::Cli::file_for_reading('input', $opts{input}); + + my @htsfiles = @ARGV; + for my $hts(@htsfiles) { + PCAP::Cli::file_for_reading('bam/cram files', $hts); + } + $opts{'htsfiles'} = \@htsfiles; + + return \%opts; + +} + +__END__ + +=head1 NAME + +pindelCohortVafFill.pl - Takes a VCF and adds VAF for sample/event with no call. + +=head1 SYNOPSIS + +pindelCohortVafFill.pl [options] SAMPLE_1.bam SAMPLE_2.bam [...] + + SAMPLE.bam should have co-located *.bai and *.bas files. + + Required parameters: + -input -i VCF file to read in. + + Optional parameters: + -suffix -s Suffix for updated file [.vaf] + + Other: + -help -h Brief help message. + -man -m Full documentation. + -version -v Prints the version number. + +=head1 DESCRIPTION + +B Fills in VAF for sample/event combinations where no call was made. + +There must be a BAM/CRAM for every sample indicated by the VCF header. + +=cut diff --git a/perl/bin/pindelCohortVafSplit.pl b/perl/bin/pindelCohortVafSplit.pl new file mode 100755 index 0000000..83fc606 --- /dev/null +++ b/perl/bin/pindelCohortVafSplit.pl @@ -0,0 +1,124 @@ +#!/usr/bin/env perl + +use strict; +use warnings FATAL => 'all'; +use autodie qw(:all); +use Cwd qw(abs_path); +use Pod::Usage qw(pod2usage); +use FindBin qw($Bin); +use lib "$Bin/../lib"; +use Getopt::Long; +use File::Path qw(make_path); +use File::Spec::Functions; +use IO::Uncompress::Gunzip qw(gunzip $GunzipError); +use PCAP::Cli; + +{ + my $options = setup(); + split_data($options->{input}, $options->{output}, $options->{size}); +} + +sub split_data { + my ($input, $outdir, $max_e) = @_; + make_path($outdir); + my $part_fmt = '%s/%04d.vcf'; + my @header; + my $no_vaf_count = 0; + my $no_vaf_file_no = 0; + + my $complete_rec = catfile($outdir, 'complete_rec.vcf'); + my $no_vaf_file = sprintf $part_fmt, $outdir, $no_vaf_file_no++; + + open my $COMP, '>', $complete_rec; + my $NO_VAF; + + my $z = IO::Uncompress::Gunzip->new($input, MultiStream => 1) or die "gunzip failed: $GunzipError\n"; + while(my $line = <$z>) { + if($line =~ m/^#/) { + push @header, $line; + print $COMP $line; + next; + } + unless($NO_VAF) { + warn "Creating $no_vaf_file\n"; + open $NO_VAF, '>', $no_vaf_file; + print $NO_VAF join q{}, @header; + } + chomp $line; + # col 9+ containing '.' means work to be done + my ($chr, $pos, $id, $ref, $alt, $qual, $filter, $info, $format, @samples) = split /\t/, $line; + my $n_vaf = 0; + map { if($_ eq q{.}) {$n_vaf++} } @samples; + if($n_vaf) { + print $NO_VAF $line."\n"; + $no_vaf_count += $n_vaf; + if($no_vaf_count >= $max_e) { + close $NO_VAF; + $no_vaf_file = sprintf $part_fmt, $outdir, $no_vaf_file_no++; + warn "\nCreating $no_vaf_file\n"; + open $NO_VAF, '>', $no_vaf_file; + print $NO_VAF join q{}, @header; + $no_vaf_count = 0; + } + } + else { + print $COMP $line."\n"; + } + } + close $COMP; + close $NO_VAF; +} + +sub setup { + my %opts = ( + 'size' => 10000, + ); + GetOptions( 'h|help' => \$opts{h}, + 'm|man' => \$opts{'m'}, + 'v|version' => \$opts{v}, + 'i|input=s' => \$opts{input}, + 'o|output=s' => \$opts{output}, + 's|size:i' => \$opts{size}, + ); + + if(defined $opts{v}) { + printf "Version: %s\n", Sanger::CGP::Pindel::Implement->VERSION; + exit; + } + pod2usage(-verbose => 1) if(defined $opts{h}); + pod2usage(-verbose => 2) if(defined $opts{'m'}); + + PCAP::Cli::file_for_reading('input', $opts{input}); + + return \%opts; +} + +__END__ + +=head1 NAME + +pindelCohortVafSplit.pl - Takes merged cohort VCF and splits into even sized files for VAF fill-in. + +=head1 SYNOPSIS + +pindelCohortVafSplit.pl [options] + + Required parameters: + -input -i VCF file to read in. + -output -o Directory for split VCFs + + Optional parameters: + -size -s Number of Sample/event combinations per-file [10000] + + Other: + -help -h Brief help message. + -man -m Full documentation. + -version -v Prints the version number. + +=head1 DESCRIPTION + +B will generate a set of split files for VAF fill-in processing. + +One additional file is generated containg events where no fill-in is required. + +=cut diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm index 369f7aa..5c623a2 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm @@ -288,7 +288,12 @@ my $count=0; $v_d->[$V_FMT] .= $self->{fmt_ext} unless($self->{fill_in}); my $gt_pos = $V_GT_START; for my $gt_set (@{$extra_gt}) { - $v_d->[$gt_pos] = join q{:}, $v_d->[$gt_pos], @{$gt_set}; + if($v_d->[$gt_pos] eq q{.}) { + $v_d->[$gt_pos] = join q{:}, './.:.:.', @{$gt_set}; + } + else { + $v_d->[$gt_pos] = join q{:}, $v_d->[$gt_pos], @{$gt_set}; + } $gt_pos++; } printf $fh "%s\n", join "\t", @{$v_d}; diff --git a/perl/util/README.md b/perl/util/README.md new file mode 100644 index 0000000..f170a28 --- /dev/null +++ b/perl/util/README.md @@ -0,0 +1,3 @@ +# perl/util + +Scripts to aid in R&D processes, these are not installed. diff --git a/perl/util/pairedSplit.pl b/perl/util/pairedSplit.pl new file mode 100644 index 0000000..be80285 --- /dev/null +++ b/perl/util/pairedSplit.pl @@ -0,0 +1,66 @@ +#!/usr/bin/env perl + +use strict; +use v5.16; +use Data::Dumper; + +my ($t_name, $n_name, $in) = @ARGV; +open my $IF, '<', $in or die "Failed to open $in: $!"; +my ($t_pos, $n_pos); +while(my $l = <$IF>) { + next if ($l =~ m/^##/); + chomp $l; + my @bits = split /\t/, $l; + if($l =~ m/^#CHRO/) { + ($t_pos, $n_pos) = find_samples(\@bits, $t_name, $n_name); + next; + } + process_record($bits[$t_pos], $bits[$n_pos], $l); +} +close $IF; + +sub process_record { + my ($t_geno, $n_geno, $record) = @_; + if($n_geno eq q{.}) { + return; + } + if($t_geno eq q{.}) { + return; + } + else { + my (undef, $s1, $s2, $pp, $np, $wp, $wn, $wm, $mp,$mn, $mm, $vaf) = split ':', $t_geno; + return if(($mp + $mn + $wp + $wn) > 60); # example is 60x + return if($mm >= 0.035); + return if($vaf < 0.05); + return if($mp == 0); + return if($mn == 0); + return if($wp == 0); + return if($wn == 0); + say $record; + #if($mm < 0.035 && $vaf >= 0.02 && $mp > 0 && $mn > 0 && ) { + # say $record; + #} + } + +} + +sub find_samples { + my ($bits, $t_name, $n_name) = @_; + my ($t_pos, $n_pos); + my $a_s = @{$bits} - 1; + for my $i(0..$a_s) { + if($bits->[$i] eq $t_name) { + $t_pos = $i; + } + if($bits->[$i] eq $n_name) { + $n_pos = $i; + } + } + unless(defined $t_pos) { + die "Failed to find $t_name\n" + } + unless(defined $n_pos) { + die "Failed to find $n_name\n" + } + return ($t_pos, $n_pos); +} From cee0c94c35420d67f779f9b2b6c10d4e0d10da18 Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Mon, 3 Aug 2020 12:37:40 +0000 Subject: [PATCH 16/60] Working fill-in code --- perl/bin/pindelCohort.pl | 12 +- perl/bin/pindelCohortMerge.pl | 5 +- perl/bin/pindelCohortVafFill.pl | 75 ------ perl/bin/pindelCohortVafSliceFill.pl | 97 ++++++++ perl/bin/pindelCohort_to_vcf.pl | 4 +- perl/bin/pindel_blat_vaf.pl | 2 +- perl/lib/Sanger/CGP/Pindel/Implement.pm | 51 ++-- .../CGP/Pindel/OutputGen/PindelRecord.pm | 18 ++ .../Pindel/OutputGen/PindelRecordParser.pm | 26 +++ .../CGP/Pindel/OutputGen/VcfBlatAugment.pm | 99 +++++--- .../Pindel/OutputGen/VcfCohortConverter.pm | 221 +----------------- 11 files changed, 247 insertions(+), 363 deletions(-) delete mode 100644 perl/bin/pindelCohortVafFill.pl create mode 100644 perl/bin/pindelCohortVafSliceFill.pl diff --git a/perl/bin/pindelCohort.pl b/perl/bin/pindelCohort.pl index 6d95de6..155a709 100644 --- a/perl/bin/pindelCohort.pl +++ b/perl/bin/pindelCohort.pl @@ -40,7 +40,7 @@ BEGIN use Data::Dumper; const my %INDEX_MAX => ( - 'input' => -1, + 'input' => 1, 'pindel' => -1, 'parse' => 1, # reads all pout, makes raw-vcf and splits to even sized for blat 'blat' => -1, @@ -61,9 +61,7 @@ BEGIN # start processes here (in correct order obviously), add conditions for skipping based on 'process' option if(!exists $options->{'process'} || $options->{'process'} eq 'input') { - my $jobs = scalar @{$options->{'hts_files'}}; - $jobs = $options->{'limit'} if(exists $options->{'limit'} && defined $options->{'limit'}); - $threads->run($jobs, 'input', $options); + Sanger::CGP::Pindel::Implement::input_cohort($options) } if(!exists $options->{'process'} || $options->{'process'} eq 'pindel') { my $jobs = Sanger::CGP::Pindel::Implement::determine_jobs($options); # method still needed to populate info @@ -206,7 +204,11 @@ =head1 OPTIONS Available processes for this tool are: - ??? + input + pindel - index available + parse + blat - index available + concat =item B<-index> diff --git a/perl/bin/pindelCohortMerge.pl b/perl/bin/pindelCohortMerge.pl index 8e51928..d5118ba 100644 --- a/perl/bin/pindelCohortMerge.pl +++ b/perl/bin/pindelCohortMerge.pl @@ -97,12 +97,9 @@ sub collate_data { return (\%ds, \@samples); } - - - sub header { my ($output, $vcfs, $sample_head) = @_; - my $metadata_header = _command_output(sprintf q{zgrep -B 1000000 -m 1 '^#CHROM' %s | head -n -1}, $vcfs->[0]); + my $metadata_header = _command_output(sprintf q{zgrep -B 1000000 -m 1 '^#CHROM' %s | grep -v '^##SAMPLE=' | head -n -1}, $vcfs->[0]); my $col_header = _command_output(sprintf q{zgrep -m 1 '^#CHROM' %s}, $vcfs->[0]); # remove last col as has sample will be added back ${$col_header} =~ s/\t[^\t]+$//; diff --git a/perl/bin/pindelCohortVafFill.pl b/perl/bin/pindelCohortVafFill.pl deleted file mode 100644 index 23243c2..0000000 --- a/perl/bin/pindelCohortVafFill.pl +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/env perl - -use strict; -use warnings FATAL => 'all'; -use autodie qw(:all); -use Cwd qw(abs_path); -use Pod::Usage qw(pod2usage); -use FindBin qw($Bin); -use lib "$Bin/../lib"; -use Getopt::Long; - -{ - my $options = setup(); -} - -sub setup { - my %opts = ( - suffix => '.vaf', - ); - GetOptions( 'h|help' => \$opts{h}, - 'm|man' => \$opts{'m'}, - 'v|version' => \$opts{v}, - 'i|input=s' => \$opts{input}, - 's|suffix:s' => \$opts{suffix}, - ); - - if(defined $opts{v}) { - printf "Version: %s\n", Sanger::CGP::Pindel::Implement->VERSION; - exit; - } - pod2usage(-verbose => 1) if(defined $opts{h}); - pod2usage(-verbose => 2) if(defined $opts{'m'}); - - PCAP::Cli::file_for_reading('input', $opts{input}); - - my @htsfiles = @ARGV; - for my $hts(@htsfiles) { - PCAP::Cli::file_for_reading('bam/cram files', $hts); - } - $opts{'htsfiles'} = \@htsfiles; - - return \%opts; - -} - -__END__ - -=head1 NAME - -pindelCohortVafFill.pl - Takes a VCF and adds VAF for sample/event with no call. - -=head1 SYNOPSIS - -pindelCohortVafFill.pl [options] SAMPLE_1.bam SAMPLE_2.bam [...] - - SAMPLE.bam should have co-located *.bai and *.bas files. - - Required parameters: - -input -i VCF file to read in. - - Optional parameters: - -suffix -s Suffix for updated file [.vaf] - - Other: - -help -h Brief help message. - -man -m Full documentation. - -version -v Prints the version number. - -=head1 DESCRIPTION - -B Fills in VAF for sample/event combinations where no call was made. - -There must be a BAM/CRAM for every sample indicated by the VCF header. - -=cut diff --git a/perl/bin/pindelCohortVafSliceFill.pl b/perl/bin/pindelCohortVafSliceFill.pl new file mode 100644 index 0000000..7749ac7 --- /dev/null +++ b/perl/bin/pindelCohortVafSliceFill.pl @@ -0,0 +1,97 @@ +#!/usr/bin/env perl + +use strict; +use warnings FATAL => 'all'; +use autodie qw(:all); +use Cwd qw(abs_path); +use Pod::Usage qw(pod2usage); +use FindBin qw($Bin); +use lib "$Bin/../lib"; +use Getopt::Long; + +use PCAP::Cli; +use Sanger::CGP::Pindel::OutputGen::VcfBlatAugment; + +{ + my $options = setup(); + my $augment = Sanger::CGP::Pindel::OutputGen::VcfBlatAugment->new( + input => $options->{input}, + ref => $options->{ref}, + ofh => $options->{output}, + sam => $options->{align}, + hts_files => $options->{hts_files}, + outpath => $options->{outpath}, + fill_in => 1, + ); + + $augment->output_header; + $augment->process_records; + +} + +sub setup { + my %opts = ( + ); + GetOptions( 'h|help' => \$opts{h}, + 'm|man' => \$opts{'m'}, + 'v|version' => \$opts{v}, + 'i|input=s' => \$opts{input}, + 'r|ref=s' => \$opts{ref}, + 'o|output=s' => \$opts{output}, + ); + + if(defined $opts{v}) { + printf "Version: %s\n", Sanger::CGP::Pindel::Implement->VERSION; + exit; + } + pod2usage(-verbose => 1) if(defined $opts{h}); + pod2usage(-verbose => 2) if(defined $opts{'m'}); + + PCAP::Cli::file_for_reading('input', $opts{input}); + PCAP::Cli::file_for_reading('ref', $opts{ref}); + + $opts{align} = $opts{output}.'.fill.sam' unless(defined $opts{align}); + + my @htsfiles = @ARGV; + for my $hts(@htsfiles) { + PCAP::Cli::file_for_reading('bam/cram files', $hts); + } + $opts{'hts_files'} = \@htsfiles; + + $opts{outpath} = $opts{output}; + open my $ofh, '>', $opts{output}; + $opts{output} = $ofh; + + return \%opts; + +} + +__END__ + +=head1 NAME + +pindelCohortVafSliceFill.pl - Takes a VCF and adds VAF for sample/event with no call. + +=head1 SYNOPSIS + +pindelCohortVafSliceFill.pl [options] SAMPLE_1.bam SAMPLE_2.bam [...] + + SAMPLE*.bam should have co-located *.bai and *.bas files. + + Required parameters: + -ref -r File path to the reference file used to provide the coordinate system. + -input -i VCF file to read in. + -output -o File path for VCF output (not compressed), colcated sample bams + + Other: + -help -h Brief help message. + -man -m Full documentation. + -version -v Prints the version number. + +=head1 DESCRIPTION + +B Fills in VAF for sample/event combinations where no call was made. + +There must be a BAM/CRAM for every sample indicated by the VCF header. + +=cut diff --git a/perl/bin/pindelCohort_to_vcf.pl b/perl/bin/pindelCohort_to_vcf.pl index 620bfe5..bafe3f0 100644 --- a/perl/bin/pindelCohort_to_vcf.pl +++ b/perl/bin/pindelCohort_to_vcf.pl @@ -230,7 +230,9 @@ sub setup{ my @full_inputs; for my $if(@{$opts{'input'}}) { if($if =~ s/%/*/g) { - push @full_inputs, glob $if; + for my $gf(glob $if) { + push @full_inputs, $gf if(-s $gf); + } } else { push @full_inputs, $if; diff --git a/perl/bin/pindel_blat_vaf.pl b/perl/bin/pindel_blat_vaf.pl index bc772c1..cde7fa8 100755 --- a/perl/bin/pindel_blat_vaf.pl +++ b/perl/bin/pindel_blat_vaf.pl @@ -19,7 +19,7 @@ ref => $options->{ref}, ofh => $options->{output}, sam => $options->{align}, - hts_file => $options->{hts}, + hts_files => $options->{hts}, outpath => $options->{outpath}, ); diff --git a/perl/lib/Sanger/CGP/Pindel/Implement.pm b/perl/lib/Sanger/CGP/Pindel/Implement.pm index 3e90101..35a567b 100644 --- a/perl/lib/Sanger/CGP/Pindel/Implement.pm +++ b/perl/lib/Sanger/CGP/Pindel/Implement.pm @@ -59,42 +59,26 @@ const my $COHORT_2_VCF => q{ -r %s -i %s -o %s %s%s}; const my $VCF_SPLIT_SIZE => 5_000; sub input_cohort{ - my ($index, $options) = @_; - return 1 if(exists $options->{'index'} && $index != $options->{'index'}); + my ($options) = @_; my $tmp = $options->{'tmp'}; - return 1 if PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), $index); - - my @inputs = @{$options->{'hts_files'}}; - my $iter = 1; - for my $input(@inputs) { - next if($iter++ != $index); # skip to the relevant input in the list - - ## build command for this index - # - - my $max_threads = $options->{'threads'}; - unless (exists $options->{'index'}) { - $max_threads = int ($max_threads / scalar @inputs); - } - $max_threads = 1 if($max_threads == 0); + return 1 if PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), 0); - my $sample = sanitised_sample_from_bam($input); - my $gen_out = File::Spec->catdir($tmp, $sample); - make_path($gen_out) unless(-e $gen_out); + my $input = $options->{'hts_files'}->[0]; + my $max_threads = $options->{'threads'}; - my $command = "$^X "; - $command .= _which('pindel_input_gen.pl'); - $command .= sprintf $PINDEL_GEN_COMM, $input, $gen_out, $max_threads; - $command .= " -r $options->{reference}"; - $command .= " -e $options->{badloci}" if(exists $options->{'badloci'}); + my $sample = sanitised_sample_from_bam($input); + my $gen_out = File::Spec->catdir($tmp, $sample); + make_path($gen_out) unless(-e $gen_out); - PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), $command, $index); + my $command = "$^X "; + $command .= _which('pindel_input_gen.pl'); + $command .= sprintf $PINDEL_GEN_COMM, $input, $gen_out, $max_threads; + $command .= " -r $options->{reference}"; + $command .= " -e $options->{badloci}" if(exists $options->{'badloci'}); - # - ## The rest is auto-magical - PCAP::Threaded::touch_success(File::Spec->catdir($tmp, 'progress'), $index); - } + PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), $command, 0); + PCAP::Threaded::touch_success(File::Spec->catdir($tmp, 'progress'), 0); return 1; } @@ -282,7 +266,7 @@ sub concat { my $samtools = _which('samtools'); my $command = sprintf q{(%s view -H %s | grep -P '^@(HD|SQ)' && sort %s | uniq) | %s sort -l 0 -T %s - | %s calmd -b - %s > %s}, $samtools, $hts_input, - File::Spec->catfile($vcf, 'blat_*.vcf.sam'), + File::Spec->catfile($vcf, sprintf 'blat_*.%s.sam', $sample_name), $samtools, File::Spec->catfile($vcf, 'srt'), $samtools, $options->{'reference'}, $bam; PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), ['set -o pipefail', $command], 'calmd'); @@ -322,12 +306,11 @@ sub blat { my $blat_file = $split_file; $blat_file =~ s/split_([a-z]+)/blat_$1/; my $command = $^X.' '._which('pindel_blat_vaf.pl'); - $command .= sprintf q{ -r %s -hts %s -i %s -o %s -p %s}, + $command .= sprintf q{ -r %s -hts %s -i %s -o %s}, $options->{'reference'}, $options->{hts_files}->[0], $split_file, - $blat_file, - $options->{pad}; + $blat_file; PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), $command, $index); PCAP::Threaded::touch_success(File::Spec->catdir($tmp, 'progress'), $index); diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/PindelRecord.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/PindelRecord.pm index 4d85fe1..29abd6e 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/PindelRecord.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/PindelRecord.pm @@ -259,3 +259,21 @@ sub repeats{ $self->{_repeats} = $value if defined $value; return $self->{_repeats}; } + +sub gc_5p{ + my($self,$value) = @_; + $self->{_gc_5p} = $value if defined $value; + return $self->{_gc_5p}; +} + +sub gc_3p{ + my($self,$value) = @_; + $self->{_gc_3p} = $value if defined $value; + return $self->{_gc_3p}; +} + +sub gc_rng{ + my($self,$value) = @_; + $self->{_gc_rng} = $value if defined $value; + return $self->{_gc_rng}; +} diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/PindelRecordParser.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/PindelRecordParser.pm index 3ccb057..8dbf3f7 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/PindelRecordParser.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/PindelRecordParser.pm @@ -127,6 +127,8 @@ sub _process_record{ _parse_header_v02($record,$record_header); } + + my $alignments = []; # Collect all the reads... @@ -139,6 +141,30 @@ sub _process_record{ $self->_parse_alignment($record, $alignments, \$ref_line); + # this is where we can add additional information about GC content + my $chr = $record->chro; + my $lhs_end = $record->range_start; + my $rhs_start = $record->range_end; + my $fai = $self->{_fai}; + my $r_type = $record->type; + + my $seq = $fai->fetch(sprintf '%s:%d-%d', $chr, $lhs_end - 199, $lhs_end); + my $lhs_gc = ($seq =~ tr/GCgc//)/200; + + $seq = $fai->fetch(sprintf '%s:%d-%d', $chr, $rhs_start, $rhs_start + 199); + my $rhs_gc = ($seq =~ tr/GCgc//)/200; + my $ref_tmp; + if($record->type eq 'D'){ + $ref_tmp = $record->ref_seq; + } + else { + $ref_tmp = $record->alt_seq; + } + my $rng_gc = ($ref_tmp =~ tr/GCgc//) / length $ref_tmp; + $record->gc_5p($lhs_gc); + $record->gc_3p($rhs_gc); + $record->gc_rng($rng_gc); + return $record; } diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm index 5c623a2..1452c42 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm @@ -61,7 +61,7 @@ sub new{ input => $args{input}, ref => $args{ref}, ofh => $args{ofh}, # vcf - hts_file => $args{hts_file}, + hts_files => $args{hts_files}, fill_in => $args{fill_in}, }; bless $self, $class; @@ -100,6 +100,8 @@ sub _align_output { $outpath =~ s/\.vcf$//; for my $sample(@{$self->{vcf_sample_order}}) { my $sam_file = sprintf '%s.%s.sam', $outpath, $sample; + $self->{samfile}->{$sample} = $sam_file; + $self->{bamfile}->{$sample} = sprintf '%s.%s.bam', $outpath, $sample; unlink $sam_file if(-e $sam_file); open my $SAM, '>', $sam_file; $self->{sfh}->{$sample} = $SAM; @@ -117,6 +119,7 @@ sub _sample_order { } $self->{vcf_sample_pos} = \%samp_pos; $self->{vcf_sample_order} = \@ordered_samples; + $self->{vcf_sample_count} = scalar @ordered_samples; return 1; } @@ -181,7 +184,7 @@ sub _buffer_sizes { sub _hts { my $self = shift; - for my $hts(@{$self->{'hts_file'}}) { + for my $hts(@{$self->{hts_files}}) { my $tmp = Bio::DB::HTS->new(-bam => $hts, -fasta => $self->{ref}); my $sample = $self->_sample_from_hts($tmp); if(exists $self->{hts}->{$sample}) { @@ -219,13 +222,13 @@ sub _add_headers { $vcf->add_header_line({'key'=>'source', 'value' => basename($0)}, 'append' => 1); my @format = ( - {key => 'FORMAT', ID => 'WTP', Number => 1, Type => 'Integer', Description => '+ve strand reads BLATed (or count for large deletions) to reference sequence at this location'}, - {key => 'FORMAT', ID => 'WTN', Number => 1, Type => 'Integer', Description => '-ve strand reads BLATed (or count for large deletions) to reference sequence at this location'}, - {key => 'FORMAT', ID => 'WTM', Number => 1, Type => 'Float', Description => 'Mismatch fraction of reads BLATed to reference sequence at this location (to 3 d.p.)'}, - {key => 'FORMAT', ID => 'MTP', Number => 1, Type => 'Integer', Description => '+ve strand reads BLATed to alternate sequence at this location'}, - {key => 'FORMAT', ID => 'MTN', Number => 1, Type => 'Integer', Description => '-ve strand reads BLATed to alternate sequence at this location'}, - {key => 'FORMAT', ID => 'MTM', Number => 1, Type => 'Float', Description => 'Mismatch fraction of reads BLATed to alternate sequence at this location (to 3 d.p.)'}, - {key => 'FORMAT', ID => 'VAF', Number => 1, Type => 'Float', Description => 'Variant allele fraction using reads that unambiguously map to ref or alt seq (to 3 d.p.)'}, + {key => 'FORMAT', ID => 'WTP', Number => 1, Type => 'Integer', Description => q{+ve strand reads BLATed to reference sequence at this location, input alignment depth when WTM='.'}}, + {key => 'FORMAT', ID => 'WTN', Number => 1, Type => 'Integer', Description => q{-ve strand reads BLATed to reference sequence at this location, input alignment depth when WTM='.'}}, + {key => 'FORMAT', ID => 'WTM', Number => 1, Type => 'Float', Description => q{Mismatch fraction of reads BLATed to reference sequence at this location (3 d.p.), '.' when no reads found via BLAT}}, + {key => 'FORMAT', ID => 'MTP', Number => 1, Type => 'Integer', Description => q{+ve strand reads BLATed to alternate sequence at this location}}, + {key => 'FORMAT', ID => 'MTN', Number => 1, Type => 'Integer', Description => q{-ve strand reads BLATed to alternate sequence at this location}}, + {key => 'FORMAT', ID => 'MTM', Number => 1, Type => 'Float', Description => q{Mismatch fraction of reads BLATed to alternate sequence at this location (3 d.p.), '.' when no reads found via BLAT}}, + {key => 'FORMAT', ID => 'VAF', Number => 1, Type => 'Float', Description => q{Variant allele fraction using reads that unambiguously map to ref or alt seq (3 d.p.)'}}, ); $self->{fmt_ext} = q{}; for my $f(@format) { @@ -252,7 +255,7 @@ sub to_data_hash { my ($key,$val) = split(/=/,$info); # all the values we need are key/val next unless(defined $val); - die "Clash between INFO and columns" if(exists $out{$key}); + die "Clash between INFO and columns '$key'" if(exists $out{$key}); $out{$key} = $val; } @@ -281,29 +284,22 @@ sub process_records { my $fh = $self->{ofh}; my $count=0; while(my $v_d = $self->{vcf}->next_data_array) { + $v_d->[$V_FMT] .= $self->{fmt_ext} unless($self->{fill_in}); + #$count++; #next if($count != 1); - my $v_h = $self->to_data_hash($v_d); - my $extra_gt = $self->blat_record($v_h); - $v_d->[$V_FMT] .= $self->{fmt_ext} unless($self->{fill_in}); - my $gt_pos = $V_GT_START; - for my $gt_set (@{$extra_gt}) { - if($v_d->[$gt_pos] eq q{.}) { - $v_d->[$gt_pos] = join q{:}, './.:.:.', @{$gt_set}; - } - else { - $v_d->[$gt_pos] = join q{:}, $v_d->[$gt_pos], @{$gt_set}; - } - $gt_pos++; - } + $self->blat_record($v_d); printf $fh "%s\n", join "\t", @{$v_d}; #last; } $self->_close_sams; + $self->sam_to_bam; } sub blat_record { - my ($self, $v_h) = @_; + my ($self, $v_d) = @_; + my $v_h = $self->to_data_hash($v_d); + # now attempt the blat stuff my ($fh_target, $file_target) = tempfile( SUFFIX => '.fa', UNLINK => 1 ); $self->blat_ref_alt($fh_target, $v_h); @@ -316,11 +312,23 @@ sub blat_record { $v_h->{change_pos_low} = $change_pos_low; $v_h->{change_pos_high} = $change_pos_high; - my @blat_sets; + my $gt_pos = $V_GT_START-1; for my $sample(@{$self->{vcf_sample_order}}) { - push @blat_sets, $self->blat_reads($v_h, $file_target, $sample); + $gt_pos++; + if($self->{fill_in} && $v_d->[$gt_pos] ne q{.}) { + next; + } + my $gt_set = $self->blat_reads($v_h, $file_target, $sample); + if($v_d->[$gt_pos] eq q{.}) { + $v_d->[$gt_pos] = join q{:}, './.:.:.', @{$gt_set}; + } + else { + $v_d->[$gt_pos] = join q{:}, $v_d->[$gt_pos], @{$gt_set}; + } } - return \@blat_sets; + # tempfile unlink only does it on shutdown when used in this way + unlink $file_target; + return 1; } sub read_ranges { @@ -338,8 +346,14 @@ sub blat_reads { my ($fh_psl, $file_psl) = tempfile( SUFFIX => '.psl', UNLINK => 1); close $fh_psl or die "Failed to close $file_psl (psl output)"; + warn $sample.' -> '.$self->read_ranges($v_h, $sample)."\n"; + my $c_blat = sprintf $READS_AND_BLAT, $self->{hts}->{$sample}->hts_path, $self->read_ranges($v_h, $sample), $file_query, $file_target, $file_query, $file_psl, $file_psl, $file_target, $file_query; - my ($c_out, $c_err, $c_exit) = capture { system($c_blat); }; + my ($c_out, $c_err, $c_exit) = capture { system([0,255], $c_blat); }; + if($c_exit == 255 && $c_err =~ m/processed 0 reads/ms) { + # No reads found + $c_exit = 0; + } if($c_exit) { warn "An error occurred while executing $c_blat\n"; warn "\tERROR$c_err\n"; @@ -353,10 +367,14 @@ sub blat_reads { #print "$c_blat\n"; #print "$c_out\n"; + # tempfile unlink only does it on shutdown when used in this way + unlink $file_query; + unlink $file_psl; + my ($wtp, $wtn, $mtp, $mtn, $wt_bmm, $mt_bmm) = $self->psl_axt_parser(\$c_out, $v_h, $sample); my ($wtm, $mtm) = (q{.}, q{.}); my $wtr = $wtp + $wtn; - if($wtp + $wtn == 0) { + if($wtr == 0) { ($wtp, $wtn) = $self->sam_depth($v_h, $sample); $wtr = $wtp + $wtn; } @@ -368,7 +386,7 @@ sub blat_reads { $mtm = sprintf("%.3f", $mt_bmm / $mtr); } my $depth = $wtr+$mtr; - my $vaf = $depth ? sprintf("%.3f", $mtr/$depth) : 0; + my $vaf = sprintf("%.3f", $depth ? $mtr/$depth : 0); return [$wtp, $wtn, $wtm, $mtp, $mtn, $mtm, $vaf]; } @@ -540,6 +558,27 @@ sub sam_depth { return (split /\n/, $c_out); } +sub sam_to_bam { + my ($self) = @_; + for my $sample(@{$self->{vcf_sample_order}}) { + my $sam = $self->{samfile}->{$sample}; + my $bam = $self->{bamfile}->{$sample}; + my $tmp = $bam; + $tmp =~ s/bam$/tmp/; + my $command = sprintf q{bash -c 'set -o pipefail ; samtools view -uT %s %s | samtools sort -T %s -o %s -'}, + $self->{ref}, $sam, + $tmp, $bam; + my ($c_out, $c_err, $c_exit) = capture { system($command); }; + if($c_exit) { + warn "An error occurred while executing $command\n"; + warn "\tERROR$c_err\n"; + exit $c_exit; + } + unlink(glob(sprintf '%s.*.bam', $tmp)); + unlink $sam; + } +} + sub flanking_ref { my ($self, $v_h) = @_; my $ref_left = $self->{fai}->get_sequence_no_length( diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfCohortConverter.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfCohortConverter.pm index 0732e72..a6501ce 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfCohortConverter.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfCohortConverter.pm @@ -145,8 +145,9 @@ sub gen_header{ {key => 'INFO', ID => 'RE', Number => 1, Type => 'Integer', Description => 'Range end'}, {key => 'INFO', ID => 'LEN', Number => 1, Type => 'Integer', Description => 'Length'}, {key => 'INFO', ID => 'REP', Number => 1, Type => 'Integer', Description => 'Change repeat count within range'}, - {key => 'INFO', ID => 'S1', Number => 1, Type => 'Integer', Description => 'S1'}, - {key => 'INFO', ID => 'S2', Number => 1, Type => 'Float', Description => 'S2'} + {key => 'INFO', ID => 'GC5P', Number => 1, Type => 'Float', Description => 'GC content of 200 bp. 5 prime'}, + {key => 'INFO', ID => 'GCRNG', Number => 1, Type => 'Float', Description => 'GC content of deleted/inserted seq, including range'}, + {key => 'INFO', ID => 'GC3P', Number => 1, Type => 'Float', Description => 'GC content of 200 bp. 3 prime'} ); my @format = ( @@ -222,43 +223,20 @@ sub gen_record{ $ret .= '.'.$SEP; # INFO - #PC=D;RS=19432;RE=19439;LEN=3;S1=4;S2=161.407;REP=2;PRV=1 $ret .= 'PC='.$record->type().';'; $ret .= 'RS='.$record->range_start().';'; $ret .= 'RE='.$record->range_end().';'; $ret .= 'LEN='.$record->length().';'; - $ret .= 'REP='.$record->repeats().$SEP; - - # # now attempt the blat stuff - # my ($fh_target, $file_target) = tempfile( - # DIR => '/dev/shm', - # SUFFIX => '.fa', - # UNLINK => 1 - # ); - - # my ($q_start, $q_end, $change_pos, $change_ref, $change_alt) = $self->blat_ref_alt($fh_target, $record); - - # my $change_pos_low = $change_pos; - # $change_pos_low++ if($record->type eq 'I'); - # my $range_l = ($record->range_end - $record->range_start) + 1; - # my $change_pos_high = $change_pos_low + $range_l; # REF based range, adjusted in func - # my $change_l = $record->end - $record->start + 1; - # $self->{change_pos_low} = $change_pos_low; - # $self->{change_pos_high} = $change_pos_high; - # $self->{change_l} = $change_l; - # $self->{change_ref} = $change_ref; - # $self->{change_alt} = $change_alt; - # $self->{q_start} = $q_start; - # $self->{q_end} = $q_end; - # $self->{file_target} = $file_target; - # $self->{type} = $record->type; - # $self->{chr} = $record->chro; + $ret .= 'REP='.$record->repeats().';'; + $ret .= sprintf 'GC5P=%.3f;', $record->gc_5p; + $ret .= sprintf 'GCRNG=%.3f;', $record->gc_rng; + $ret .= sprintf 'GC3P=%.3f', $record->gc_3p; + $ret .= $SEP; # FORMAT $ret .= $self->{_format}; for my $samp(@{$self->{_srt_samples}}) { - #my ($wtp, $wtn, $mtp, $mtn) = $self->blat_reads($samp, $record); $ret .= $SEP; if($self->gen_all || exists $record->reads->{$samp}) { $ret .= './.:'; @@ -267,14 +245,6 @@ sub gen_record{ $ret .= q{:}; $ret .= $record->get_read_counts($samp, '+').q{:}; $ret .= $record->get_read_counts($samp, '-'); - #.q{:}; - #$ret .= $wtp.q{:}; - #$ret .= $wtn.q{:}; - #$ret .= $mtp.q{:}; - #$ret .= $mtn.q{:}; - #my $mtr = $mtp+$mtn; - #my $depth = $wtp+$wtn+$mtr; - #$ret .= $depth ? sprintf("%.3f", $mtr/$depth) : 0; } else { $ret .= $self->{_noread_gt}; @@ -303,126 +273,6 @@ sub ins_by_sample { return $self->{_ins_set}->{$sample}; } -sub TO_REMOVE_blat_reads { - my ($self, $sample, $record) = @_; - my ($fh_query, $file_query) = tempfile( - DIR => '/dev/shm', - SUFFIX => '.fa', - UNLINK => 1 - ); - close $fh_query or die "Failed to close $file_query (query reads)"; - my ($fh_psl, $file_psl) = tempfile( - DIR => '/dev/shm', - SUFFIX => '.psl', - UNLINK => 1 - ); - close $fh_psl or die "Failed to close $file_psl (psl output)"; - - - my $read_buffer = $self->ins_by_sample($sample); - my $c_blat = sprintf $READS_AND_BLAT, $self->hts_file_by_sample($sample), $self->{chr}, $self->{q_start}-$read_buffer, $self->{q_end}+$read_buffer, $file_query, $self->{file_target}, $file_query, $file_psl, $file_psl, $self->{file_target}, $file_query; - my ($c_out, $c_err, $c_exit) = capture { system($c_blat); }; - if($c_exit) { - warn "An error occurred while executing $c_blat\n"; - warn "\tERROR$c_err\n"; - exit $c_exit; - } - -# print "target.fa\n"; -# system("cat $self->{file_target}"); -# ; -# print "query.fa\n"; -# system("cat $file_query"); -# ; -# print "\n$c_out\n"; -# exit 1; - - #my ($wtp, $wtn, $mtp, $mtn) = $self->blat_counts(\$c_out, $sample); - my ($wtp, $wtn, $mtp, $mtn) = $self->psl_axt_parser(\$c_out, $sample); - if($wtp + $wtn == 0) { - ($wtp, $wtn) = $self->sam_depth($sample, $record); - } - return ($wtp, $wtn, $mtp, $mtn); -} - -sub TO_REMOVE_psl_axt_parser { - my ($self, $blat_axt) = @_; - # collate the data by readname and order by score - my @lines = split /\n/, ${$blat_axt}; - my $line_c = @lines; - # group all reads and order by score - my %reads; - for(my $i = 0; $i<$line_c; $i+=4) { - my ($id, $t_name, $t_start, $t_end, $q_name, $q_start, $q_end, $strand, $score) = split q{ }, $lines[$i]; - push @{$reads{$q_name}{$score}}, [$t_name, $t_start, $t_end, $q_name, $q_start, $q_end, $strand, $score, $lines[$i+1], $lines[$i+2]]; - } - my %TYPE_STRAND; - # sort keys for consistency - READ: for my $read(sort keys %reads) { - # get the alignment with the highest score - for my $score(sort {$b<=>$a} keys %{$reads{$read}}) { - my @records = @{$reads{$read}{$score}}; - next READ if(@records != 1); # if best score has more than one alignment it is irrelevant - my $record = $records[0]; - if($self->parse_axt_event($record) == 1) { - $TYPE_STRAND{$record->[0].$record->[6]} += 1; - } - next READ; # remaining items are worse alignments - } - } - my $wtp = $TYPE_STRAND{'REF+'} || 0; - my $wtn = $TYPE_STRAND{'REF-'} || 0; - my $mtp = $TYPE_STRAND{'ALT+'} || 0; - my $mtn = $TYPE_STRAND{'ALT-'} || 0; - return ($wtp, $wtn, $mtp, $mtn); -} - -sub TO_REMOVE_parse_axt_event { - my ($self, $rec) = @_; - my ($t_name, $t_start, $t_end, $q_name, $q_start, $q_end, $strand, $score, $t_seq, $q_seq) = @{$rec}; - - # specific to deletion class - my $change_seq = $self->{change_ref}; - my $change_pos_high = $self->{change_pos_high}; - if($t_name eq 'ALT') { - $change_pos_high -= $self->{change_l}; - $change_seq = $self->{change_alt}; - } - $self->{change_seq} = $change_seq; - - # all the reads that don't span the range - return 0 unless($t_start <= $self->{change_pos_low} && $t_end > $change_pos_high); - # look for the change (or absence) where we expect it - my $exp_pos = $self->{change_pos_low} - $t_start; - if(index($t_seq, $change_seq, $exp_pos) == $exp_pos) { -# print join "\t", $t_name, $t_start, $t_end, $q_name, $q_start, $q_end, $strand, $score; -# printf "\n%s\n%s\n", $t_seq, $q_seq; -# print substr($t_seq, ($self->{change_pos_low} - $t_start), $change_pos_high)."\n"; -# print "$change_seq\n"; -# print "EXP: $exp_pos\n"; -# print "INDEX: ".index($t_seq, $change_seq, $exp_pos)."\n"; - return 1; - } - -#; -# return 2; - return 0; -} - - -sub TO_REMOVE_sam_depth { - my ($self, $sample, $record) = @_; - my $mid_point = int ($record->range_start + (($record->range_end - $record->range_start)*0.5)); - my $c_samcount = sprintf $SAM_DEPTH_PN, $self->hts_file_by_sample($sample), $record->chro, $mid_point, $mid_point; - my ($c_out, $c_err, $c_exit) = capture { system($c_samcount); }; - if($c_exit) { - warn "An error occurred while executing $c_samcount\n"; - warn "\tERROR$c_err\n"; - exit $c_exit; - } - return (split /\n/, $c_out); -} - sub _dump_rec_detail { my $self = shift; for my $k(sort qw(change_pos_low change_pos_high change_l change_ref change_alt q_start q_end type chr)) { @@ -431,58 +281,3 @@ sub _dump_rec_detail { print "\n"; return 0; } - -sub TO_REMOVE_blat_ref_alt { - my ($self, $fh, $record) = @_; - my $ref_left = $record->ref_left; - my $ref_right = $record->ref_right; - my $ref = q{}; - my $alt = q{}; # save an object lookup - my $change_at = length $ref_left; - - if($record->type eq 'D') { - $ref = uc $record->ref_seq; - #nothing to add for alt - } elsif($record->type eq 'I') { - #nothing to add for ref - $alt = $record->alt_seq; - $change_at -= 1; # force base before - } - else { # must be DI - $ref = $record->ref_seq; - $alt = $record->alt_seq; - } - - my $q_start = $record->start - $change_at; # correcting for position handled in change_at - my $q_end = $record->end + length $ref_right; - - print $fh sprintf ">REF\n%s%s%s\n", $ref_left, $ref, $ref_right or die "Failed to write REF to blat ref temp file"; - print $fh sprintf ">ALT\n%s%s%s\n", $ref_left, $alt, $ref_right or die "Failed to write ALT to blat ref temp file"; - close $fh or die "Failed to close blat ref temp file"; - - my $seq_left = substr($ref_left, -1); - # -1 as includes the base before and after which would be -2 but need to correct for coord maths - # (for Del and Ins, unsure about DI at the moment) - my $seq_right; - if($record->type eq 'D') { - $seq_right = substr($ref_right, 0, ($record->range_end - $record->range_start) - 1); - } - elsif($record->type eq 'I') { - $seq_right = substr($ref_right, 0, ($record->range_end - $record->range_start)); - } - - - my $change_ref = uc ($seq_left.$ref.$seq_right); - my $change_alt = uc ($seq_left.$alt.$seq_right); - -# printf "%s\n", $record->ref_left; -# printf "%s\n", $record->ref_seq; -# printf "%s\n", $record->alt_seq; -# printf "%s\n", $record->ref_right; -# printf "%s\n", $record->type; -# printf "%s\n", $change_ref; -# printf "%s\n", $change_alt; -# #exit; - - return $q_start, $q_end, $change_at, $change_ref, $change_alt; -} From 67b644582a39a0937c6ba1d1924108e826c45fcb Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Thu, 6 Aug 2020 15:00:42 +0000 Subject: [PATCH 17/60] Finalise last of cohort processing scripts --- perl/bin/pindelCohortVafFill.pl | 181 ++++++++++++++++++ perl/bin/pindelCohortVafSliceFill.pl | 2 +- perl/lib/Sanger/CGP/Pindel/Implement.pm | 98 +++++++++- .../CGP/Pindel/OutputGen/VcfBlatAugment.pm | 91 ++------- 4 files changed, 290 insertions(+), 82 deletions(-) create mode 100644 perl/bin/pindelCohortVafFill.pl diff --git a/perl/bin/pindelCohortVafFill.pl b/perl/bin/pindelCohortVafFill.pl new file mode 100644 index 0000000..5150bbb --- /dev/null +++ b/perl/bin/pindelCohortVafFill.pl @@ -0,0 +1,181 @@ +#!/usr/bin/env perl + +use strict; +use warnings FATAL => 'all'; +use autodie qw(:all); +use Cwd qw(abs_path); +use Pod::Usage qw(pod2usage); +use FindBin qw($Bin); +use lib "$Bin/../lib"; +use Getopt::Long; +use File::Spec::Functions; +use File::Path qw(make_path remove_tree); +use Const::Fast qw(const); +use File::Copy qw(move); + +use PCAP::Cli; +use PCAP::Threaded; +use Sanger::CGP::Pindel::Implement; + +use Data::Dumper; + +const my %INDEX_MAX => ( + 'split' => 1, + 'fill' => -1, # depends on number of splits + 'bams' => -1, # may want to make multi but probably not necessary + 'finalise' => 1, # merging of split files + ); +const my @VALID_PROCESS => keys %INDEX_MAX; + +{ + my $options = setup(); + my $threads = PCAP::Threaded->new($options->{'threads'}); + + if(!exists $options->{'process'} || $options->{'process'} eq 'split') { + Sanger::CGP::Pindel::Implement::cohort_split($options); + } + + if(!exists $options->{'process'} || $options->{'process'} eq 'fill') { + for my $f(glob(catfile($options->{'split_dir'}, '*.vcf'))) { + push @{$options->{split_files}}, $f if($f =~ m{/\d+.vcf$}); + } + $threads->add_function('fill', \&Sanger::CGP::Pindel::Implement::fill_split_vaf); + $threads->run(scalar @{$options->{split_files}}, 'fill', $options) + } + + if(!exists $options->{'process'} || $options->{'process'} eq 'bams') { + $threads->add_function('bams', \&Sanger::CGP::Pindel::Implement::merge_vaf_bams); + $threads->run(scalar @{$options->{primary_hts}}, 'bams', $options) + } + + if(!exists $options->{'process'} || $options->{'process'} eq 'finalise') { + Sanger::CGP::Pindel::Implement::fill_vcf_merge($options); + move(catdir($options->{tmp}, 'logs'), catdir($options->{output}, 'logs')); + remove_tree($options->{tmp}); + } + + +} + +sub setup { + my %opts = ( + 'size' => 10000, + 'name' => 'cohort', + 'primary_hts' => [], + 'secondary_hts' => [], + 'threads' => 1, + ); + GetOptions( 'h|help' => \$opts{h}, + 'm|man' => \$opts{'m'}, + 'v|version' => \$opts{v}, + 'f|input=s' => \$opts{input}, + 'r|ref=s' => \$opts{ref}, + 'o|output=s' => \$opts{output}, + 's|size:i' => \$opts{size}, + 'n|name:s' => \$opts{name}, + 'c|cpus:i' => \$opts{'threads'}, + 'p|process:s' => \$opts{'process'}, + 'i|index:i' => \$opts{'index'}, + 'l|limit:i' => \$opts{'limit'}, + 'a|abort' => \$opts{'abort'}, + ); + + if(defined $opts{v}) { + printf "Version: %s\n", Sanger::CGP::Pindel::Implement->VERSION; + exit; + } + pod2usage(-verbose => 1) if(defined $opts{h}); + pod2usage(-verbose => 2) if(defined $opts{'m'}); + + + delete $opts{'process'} unless(defined $opts{'process'}); + delete $opts{'index'} unless(defined $opts{'index'}); + delete $opts{'limit'} unless(defined $opts{'limit'}); + + for my $param(qw(input ref output)) { + pod2usage(-verbose => 1, -message=> sprintf('ERROR: -%s must be defined', $param), -exit => 2) unless(defined $opts{$param}); + } + + my $final_logs = catdir($opts{output}, 'logs'); + if(-e $final_logs) { + warn "WARN: $final_logs directory exists suggesting completed analysis.\n"; + if($opts{abort}) { + die "EXIT as -abort in effect.\n"; + } + else { + warn "WARN: Continuing, set -abort to prevent reprocessing of completed data.\n"; + } + } + + PCAP::Cli::file_for_reading('input', $opts{input}); + PCAP::Cli::file_for_reading('ref', $opts{ref}); + + if(@ARGV < 2) { + die "Error: At least 2 sample.bam/cram datasets are requied." + } + + my @hts_pairs = @ARGV; + for my $hts_pair(@hts_pairs) { + my ($primary, $secondary) = split /:/, $hts_pair; + PCAP::Cli::file_for_reading('bam/cram files', $primary); + PCAP::Cli::file_for_reading('bam/cram files', $secondary); + push @{$opts{primary_hts}}, $primary; + push @{$opts{secondary_hts}}, $secondary; + } + + $opts{tmp} = catdir($opts{output}, 'tmpCohortVafFill'); + make_path($opts{tmp}); + + $opts{split_dir} = catdir($opts{tmp}, 'split'); + make_path($opts{split_dir}); + + make_path(catdir($opts{tmp}, 'logs')); + + return \%opts; +} + +__END__ + +=head1 NAME + +pindelCohortVafFill.pl - Takes merged cohort VCF and fills in gaps in farm friendly manner. + +=head1 SYNOPSIS + +pindelCohortVafFill.pl [options] -i ... -o ... -r ... SAMPLE.bam:PINDEL.bam ... + + Required parameters: + -file -f VCF file to read in. + -output -o Workspace directory and final output. + -ref -r File path to the reference file used to provide the coordinate system. + + Optional parameters: + -name -n Stub name for final output files [$output/cohort...] + -size -s Number of Sample/event combinations per-file when processing [10000] + -abort -a Abort noisily if data appears to have been processed (silent exit otherwise) + -cpus -c Number of cores to use. [1] + + + Targeted processing (further detail under OPTIONS): + -process -p Only process this step then exit, optionally set -index + -index -i Optionally restrict '-p' to single job + -limit -l Use '-p fill -i N -l M' - do not declare '-c' + M: maximum number of scattered processes on multiple hosts + N: This instance, 1..M + + Other: + -help -h Brief help message. + -man -m Full documentation. + -version -v Prints the version number. + +=head1 DESCRIPTION + +B Takes merged cohort VCF and fills in gaps in farm friendly manner. + +One additional file is generated containg events where no fill-in is required. + +Each original sample BAM/CRAM (normally BWA mapping) needs to be paired with the one generated by pindelCohort.pl + +Each sample BAM/CRAM also needs colocated index and bas file. + +=cut diff --git a/perl/bin/pindelCohortVafSliceFill.pl b/perl/bin/pindelCohortVafSliceFill.pl index 7749ac7..b0dc0ff 100644 --- a/perl/bin/pindelCohortVafSliceFill.pl +++ b/perl/bin/pindelCohortVafSliceFill.pl @@ -26,7 +26,7 @@ $augment->output_header; $augment->process_records; - + $augment->sam_to_bam; } sub setup { diff --git a/perl/lib/Sanger/CGP/Pindel/Implement.pm b/perl/lib/Sanger/CGP/Pindel/Implement.pm index 35a567b..7f558ff 100644 --- a/perl/lib/Sanger/CGP/Pindel/Implement.pm +++ b/perl/lib/Sanger/CGP/Pindel/Implement.pm @@ -264,7 +264,7 @@ sub concat { # now deal with the sam files unless(PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), 'calmd')) { my $samtools = _which('samtools'); - my $command = sprintf q{(%s view -H %s | grep -P '^@(HD|SQ)' && sort %s | uniq) | %s sort -l 0 -T %s - | %s calmd -b - %s > %s}, + my $command = sprintf q{(%s view -H %s | grep -P '^@(HD|SQ)' && grep -vP '^@(HD|SQ)' %s | sort | uniq) | %s sort -l 0 -T %s - | %s calmd -b - %s > %s}, $samtools, $hts_input, File::Spec->catfile($vcf, sprintf 'blat_*.%s.sam', $sample_name), $samtools, File::Spec->catfile($vcf, 'srt'), @@ -399,14 +399,14 @@ sub flag { my $tmp = $options->{'tmp'}; return 1 if PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), 0); -# FlagVcf.pl -# -r ~kr2/GitHub/cgpPindel/perl/rules/genomicRules.lst -# -sr ~kr2/GitHub/cgpPindel/perl/rules/softRules.lst -# -a /lustre/scratch112/sanger/cgppipe/nst_pipe/test_ref/human/37/e58/vagrent/codingexon_regions.indel.bed.gz -# -u /lustre/scratch112/sanger/kr2/pan_cancer_test_sets/pindel_np_gen/huge_file.gff3.gz -# -s /lustre/scratch112/sanger/cgppipe/nst_pipe/test_ref/human/37/gsm_reference_repeat.gff.gz -# -i pindel_farm/PD13371a_vs_PD13371b.vcf.gz -# -o pindel_farm/PD13371a_vs_PD13371b.flag_new_np.github.vcf + # FlagVcf.pl + # -r ~kr2/GitHub/cgpPindel/perl/rules/genomicRules.lst + # -sr ~kr2/GitHub/cgpPindel/perl/rules/softRules.lst + # -a /lustre/scratch112/sanger/cgppipe/nst_pipe/test_ref/human/37/e58/vagrent/codingexon_regions.indel.bed.gz + # -u /lustre/scratch112/sanger/kr2/pan_cancer_test_sets/pindel_np_gen/huge_file.gff3.gz + # -s /lustre/scratch112/sanger/cgppipe/nst_pipe/test_ref/human/37/gsm_reference_repeat.gff.gz + # -i pindel_farm/PD13371a_vs_PD13371b.vcf.gz + # -o pindel_farm/PD13371a_vs_PD13371b.flag_new_np.github.vcf my $stub = File::Spec->catfile($options->{'outdir'}, $options->{'tumour_name'}.'_vs_'.$options->{'normal_name'}); @@ -720,6 +720,86 @@ sub cohort_files { return 1; } +sub cohort_split { + my $options = shift; + + my $tmp = $options->{'tmp'}; + return 1 if PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), 0); + + my $command = $^X.' '._which('pindelCohortVafSplit.pl'); + $command .= sprintf ' -i %s -o %s -s %d', $options->{input}, $options->{split_dir}, $options->{size}; + + PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), $command, 0); + + PCAP::Threaded::touch_success(File::Spec->catdir($tmp, 'progress'), 0); +} + +sub fill_split_vaf { + my ($index_in, $options) = @_; + my $tmp = $options->{'tmp'}; + + return 1 if(exists $options->{'index'} && $index_in != $options->{'index'}); + + my @split_files = @{$options->{split_files}}; + + my @indicies = limited_indicies($options, $index_in, scalar @split_files); + for my $index(@indicies) { + next if PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), $index); + my $split_file = $split_files[$index-1]; + my $fill_file = $split_file =~ s/vcf$/vaf.vcf/r; + my $command = $^X.' '._which('pindelCohortVafSliceFill.pl'); + $command .= sprintf ' -r %s -i %s -o %s ', $options->{ref}, $split_file, $fill_file; + $command .= join q{ }, @{$options->{primary_hts}}; + + PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), $command, $index); + PCAP::Threaded::touch_success(File::Spec->catdir($tmp, 'progress'), $index); + } +} + +sub merge_vaf_bams { + my ($index_in, $options) = @_; + my $tmp = $options->{'tmp'}; + + return 1 if(exists $options->{'index'} && $index_in != $options->{'index'}); + my @indicies = limited_indicies($options, $index_in, scalar @{$options->{secondary_hts}}); + for my $index(@indicies) { + next if PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), $index); + my $arr_idx = $index-1; + my $sample = sanitised_sample_from_bam($options->{primary_hts}->[$arr_idx]); + + my @split_bams; + for my $f(glob(catfile($options->{'split_dir'}, sprintf '*.vaf.%s.bam', $sample))) { + push @split_bams, $f if($f =~ m{/\d+\.vaf.$sample\.bam$}); + } + my $command = _which('samtools'); + # needs to attempt to clean up duplicate reads + $command .= sprintf q{ merge --output-fmt SAM -nf - %s | pee 'grep ^@' 'grep -v ^@ | sort | uniq' | samtools view -u - | samtools sort -o %s -}, + join( q{ }, $options->{secondary_hts}->[$arr_idx], @split_bams), + catfile($options->{output}, sprintf('%s.vaf.bam', $sample)); + + PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), $command, $index); + PCAP::Threaded::touch_success(File::Spec->catdir($tmp, 'progress'), $index); + } +} + +sub fill_vcf_merge { + my $options = shift; + my $tmp = $options->{'tmp'}; + return if PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), 0); + my @split_bams; + for my $f(glob(catfile($options->{'split_dir'}, sprintf '*.vaf.vcf'))) { + push @split_bams, $f if($f =~ m{/\d+\.vaf\.vcf$}); + } + my $final_vcf = catfile($options->{output}, sprintf '%s.vaf.vcf.gz', $options->{name}); + my $merge = sprintf q{(grep '^#' %s ; grep -vh '^#' %s | sort -k1,1 -k2,2n -k 4,4 -k5,5) | bgzip -c > %s}, + $split_bams[0], join(q{ } , @split_bams), + $final_vcf; + my $tabix = sprintf q{tabix -fp vcf %s}, $final_vcf; + + PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), [$merge, $tabix], 0); + PCAP::Threaded::touch_success(File::Spec->catdir($tmp, 'progress'), 0); +} + 1; __END__ diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm index 1452c42..206ce38 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm @@ -95,8 +95,17 @@ sub _close_sams { return 1; } +sub _fa_dict { + my $self = shift; + open my $D, '<', $self->{ref}.'.dict' or die "Failed to find $self->{ref}.dict, please generate with 'samtools dict'"; + chomp(my @dict = <$D>); + close $D; + $self->{fa_dict} = \@dict; +} + sub _align_output { my ($self, $outpath) = @_; + $self->_fa_dict(); $outpath =~ s/\.vcf$//; for my $sample(@{$self->{vcf_sample_order}}) { my $sam_file = sprintf '%s.%s.sam', $outpath, $sample; @@ -104,6 +113,8 @@ sub _align_output { $self->{bamfile}->{$sample} = sprintf '%s.%s.bam', $outpath, $sample; unlink $sam_file if(-e $sam_file); open my $SAM, '>', $sam_file; + print $SAM join "\n", @{$self->{fa_dict}}; + print $SAM "\n"; $self->{sfh}->{$sample} = $SAM; } return 1; @@ -205,7 +216,6 @@ sub _add_headers { my $self = shift; my $vcf = $self->{'vcf'}; - my %options = ( input => basename($self->{input}), ref => basename($self->{ref}), @@ -221,7 +231,7 @@ sub _add_headers { $vcf->add_header_line({'key'=>'source', 'value' => basename($0)}, 'append' => 1); - my @format = ( + my @format = ( {key => 'FORMAT', ID => 'WTP', Number => 1, Type => 'Integer', Description => q{+ve strand reads BLATed to reference sequence at this location, input alignment depth when WTM='.'}}, {key => 'FORMAT', ID => 'WTN', Number => 1, Type => 'Integer', Description => q{-ve strand reads BLATed to reference sequence at this location, input alignment depth when WTM='.'}}, {key => 'FORMAT', ID => 'WTM', Number => 1, Type => 'Float', Description => q{Mismatch fraction of reads BLATed to reference sequence at this location (3 d.p.), '.' when no reads found via BLAT}}, @@ -249,7 +259,6 @@ sub to_data_hash { $out{REF} = substr $items[3], 1; $out{ALT} = substr $items[4], 1; - # parse the info block for my $info (split(/;/,$items[7])) { my ($key,$val) = split(/=/,$info); @@ -267,8 +276,6 @@ sub to_data_hash { else { $out{END} += 1; } -#print "$out{POS} - $out{END}\n"; -#exit; # skip FORMAT # parse GT @@ -282,18 +289,12 @@ sub to_data_hash { sub process_records { my $self = shift; my $fh = $self->{ofh}; -my $count=0; while(my $v_d = $self->{vcf}->next_data_array) { $v_d->[$V_FMT] .= $self->{fmt_ext} unless($self->{fill_in}); - -#$count++; -#next if($count != 1); $self->blat_record($v_d); printf $fh "%s\n", join "\t", @{$v_d}; -#last; } - $self->_close_sams; - $self->sam_to_bam; + $self->_close_sams } sub blat_record { @@ -346,8 +347,6 @@ sub blat_reads { my ($fh_psl, $file_psl) = tempfile( SUFFIX => '.psl', UNLINK => 1); close $fh_psl or die "Failed to close $file_psl (psl output)"; - warn $sample.' -> '.$self->read_ranges($v_h, $sample)."\n"; - my $c_blat = sprintf $READS_AND_BLAT, $self->{hts}->{$sample}->hts_path, $self->read_ranges($v_h, $sample), $file_query, $file_target, $file_query, $file_psl, $file_psl, $file_target, $file_query; my ($c_out, $c_err, $c_exit) = capture { system([0,255], $c_blat); }; if($c_exit == 255 && $c_err =~ m/processed 0 reads/ms) { @@ -360,13 +359,6 @@ sub blat_reads { exit $c_exit; } -## redirect output and the following will give you relevant files and the command -## $ tail -n 5 output | head -n 4 > target.fa && head -n -5 output > query.fa && tail -n 1 output -#system("cat $file_query"); -#system("cat $file_target"); -#print "$c_blat\n"; -#print "$c_out\n"; - # tempfile unlink only does it on shutdown when used in this way unlink $file_query; unlink $file_psl; @@ -407,9 +399,6 @@ sub psl_axt_parser { push @{$reads{$clean_qname}{$score}}, [$t_name, $t_start, $t_end, $q_name, $q_start, $q_end, $strand, $score, $lines[$i+1], $q_seq]; } -#print Dumper(\%reads); -my $SKIP_EVENT = q{}; - my %type_strand; my %bmm_sums; # sort keys for consistency @@ -418,9 +407,6 @@ my $SKIP_EVENT = q{}; for my $score(sort {$b<=>$a} keys %{$reads{$read}}) { my @records = @{$reads{$read}{$score}}; if(@records != 1) { # if best score has more than one alignment it is irrelevant -#print Dumper(\@records); -#print "MULTI MAX\n"; -#; next READ; } my $record = $records[0]; @@ -428,8 +414,6 @@ my $SKIP_EVENT = q{}; $type_strand{$record->[0].$record->[6]} += 1; $bmm_sums{$record->[0]} += bmm($record->[8], $record->[9]); } -#$SKIP_EVENT = unless($SKIP_EVENT eq q{1}); -#chomp $SKIP_EVENT; next READ; # remaining items are worse alignments } } @@ -437,8 +421,7 @@ my $SKIP_EVENT = q{}; my $wtn = $type_strand{'REF-'} || 0; my $mtp = $type_strand{'ALT+'} || 0; my $mtn = $type_strand{'ALT-'} || 0; -#print "waiting...\n"; -#; + return ($wtp, $wtn, $mtp, $mtn, $bmm_sums{REF}, $bmm_sums{ALT}); } @@ -464,23 +447,6 @@ sub parse_axt_event { $change_seq = $v_h->{change_alt}; } -# eval { -# my $exp_pos = $v_h->{change_pos_low} - $t_start; # duplicate of if block code -# print join "\t", $t_name, $t_start, $t_end, $q_name, $q_start, $q_end, $strand, $score; -# printf "\n%s\n%s\n", $t_seq, $q_seq; -# print "exp_pos: $exp_pos\n"; -# printf "q_seqlen: %s\n", length $q_seq; -# my $sub_q_seq = substr($q_seq, $exp_pos, length $change_seq); -# my $lpad = q{ } x $exp_pos; -# print $lpad.$change_seq."\n"; -# print $lpad.$sub_q_seq."\n"; -# my $boo = $t_start <= $v_h->{change_pos_low} && $t_end > $change_pos_high; -# print "$boo: $t_start <= $v_h->{change_pos_low} && $t_end > $change_pos_high\n"; - -# print "INDEX: ".index($q_seq, $change_seq, $exp_pos)."\n"; -# }; print "OUT OF BOUNDS\n" if $@; - - # all the reads that span the range are kept my $retval = 0; if($t_start <= ($v_h->{change_pos_low} - $PAD_EVENT) && $t_end > ($change_pos_high + $PAD_EVENT)) { @@ -499,14 +465,11 @@ sub parse_axt_event { } } } -#print "KEEP: $retval\n"; return $retval; } sub sam_record { my($self, $v_h, $rec, $sample) = @_; -# warn Dumper($v_h); -# warn Dumper($rec); my $qname = $rec->[3]; my $seq = $rec->[9]; @@ -523,25 +486,18 @@ sub sam_record { my $m_c = ($v_h->{change_pos} - $rec->[1]) + 1; $m_c += 1 if($v_h->{PC} eq 'I'); $cigar = $m_c.'M'; -#print $cigar."\n"; my $change_ref = length($v_h->{REF}); my $change_alt = length($v_h->{ALT}); if($change_ref) { $cigar .= $change_ref.'D'; -#print $cigar."\n"; } if($change_alt) { $cigar .= $change_alt.'I'; -#print $cigar."\n"; $m_c += $change_alt; # as consumes read } $cigar .= (length($seq) - $m_c).'M'; -#print $cigar."\n"; } -#printf "%s:%d-%d\n", $v_h->{CHROM}, $pos, $pos + 100; printf {$self->{sfh}->{$sample}} "%s\n", join "\t", $qname, $flag, $v_h->{CHROM}, $pos, 60, $cigar, '*', 0, 0, $seq, '*'; - -# ; } sub sam_depth { @@ -565,9 +521,10 @@ sub sam_to_bam { my $bam = $self->{bamfile}->{$sample}; my $tmp = $bam; $tmp =~ s/bam$/tmp/; - my $command = sprintf q{bash -c 'set -o pipefail ; samtools view -uT %s %s | samtools sort -T %s -o %s -'}, - $self->{ref}, $sam, - $tmp, $bam; + my $command = sprintf q{bash -c 'set -o pipefail ; samtools view -uT %s %s | samtools sort -l 0 -T %s - | samtools calmd - %s > %s'}, + $self->{ref}, $sam, # view + $tmp, # sort + $self->{'ref'}, $bam; # calmd my ($c_out, $c_err, $c_exit) = capture { system($command); }; if($c_exit) { warn "An error occurred while executing $command\n"; @@ -587,7 +544,6 @@ sub flanking_ref { ($v_h->{POS} - $self->{target_pad})+1, $v_h->{POS}, ); -#print "$ref_left\n"; my $ref_right = $self->{fai}->get_sequence_no_length( sprintf $LOCI_FMT, @@ -595,7 +551,7 @@ sub flanking_ref { $v_h->{END}, $v_h->{END} + $self->{target_pad}, ); -#print "$ref_right\n"; + return [$ref_left, $ref_right] } @@ -609,7 +565,6 @@ sub blat_ref_alt { my $change_at = length $ref_left; my $call_type = $v_h->{PC}; - # THIS MAY NOT BE CORRECT NOW if($call_type eq 'I') { $change_at -= 1; # force base before } @@ -636,17 +591,9 @@ sub blat_ref_alt { $seq_right = substr($ref_right, 0, ($r_end - $r_start)); } - my $change_ref = $seq_left.$ref.$seq_right; my $change_alt = $seq_left.$alt.$seq_right; -# printf ">REF\n%s%s%s\n", $ref_left, $ref, $ref_right; -# printf ">ALT\n%s%s%s\n", $ref_left, $alt, $ref_right; -# printf "%s - %s - %s\n", $seq_left, $ref, $seq_right; -# printf "%s - %s - %s\n", $seq_left, $alt, $seq_right; -# printf "%d - %d = %d\n", $r_end, $r_start, $r_end - $r_start; -# printf "%s %s %s\n", $q_start, $q_end, $change_at; - $v_h->{q_start} = $q_start; $v_h->{q_end} = $q_end; $v_h->{change_pos} = $change_at; From 9ae67990213d5143a8ecad20ec1cb774641f65b5 Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Fri, 7 Aug 2020 09:31:06 +0000 Subject: [PATCH 18/60] Update tests for changes to how data is passed --- perl/t/data/blat/chr10_1-23700.fa.dict | 2 ++ perl/t/vcfBlatAugment.t | 27 +++++++++++++------------- 2 files changed, 16 insertions(+), 13 deletions(-) create mode 100644 perl/t/data/blat/chr10_1-23700.fa.dict diff --git a/perl/t/data/blat/chr10_1-23700.fa.dict b/perl/t/data/blat/chr10_1-23700.fa.dict new file mode 100644 index 0000000..25c6d07 --- /dev/null +++ b/perl/t/data/blat/chr10_1-23700.fa.dict @@ -0,0 +1,2 @@ +@HD VN:1.0 SO:unsorted +@SQ SN:chr10 LN:23700 M5:5d182da12dc542ac36636bab171df9fb UR:file:///home/kr2/GitHub/cancerit/cgpPindel/perl/t/data/blat/chr10_1-23700.fa diff --git a/perl/t/vcfBlatAugment.t b/perl/t/vcfBlatAugment.t index 413a960..1449c9c 100644 --- a/perl/t/vcfBlatAugment.t +++ b/perl/t/vcfBlatAugment.t @@ -36,11 +36,11 @@ const my @HEADER_ENDS => do { const my $HEADER_LINES => 3393; const my $DATA_ARR_D => [qw(chr10 11201 id CA C 390 . PC=D;RS=11201;RE=11205;LEN=1;S1=11;S2=849.236;REP=3 GT:PP:NP ./.:10:0)]; -const my $RES_ARR_D => [12, 3, 0.015, 8, 3, 0.006, 0.423]; +const my $RES_ARR_D => [qw(chr10 11201 id CA C 390 . PC=D;RS=11201;RE=11205;LEN=1;S1=11;S2=849.236;REP=3 GT:PP:NP ./.:10:0:12:3:0.015:8:3:0.006:0.423)]; const my $DATA_ARR_DI => [qw(chr10 22777 id AGAAACTGTG ACTGTGAGATAGATATATATAGATAGATATAT 105 . PC=DI;RS=22777;RE=22787;LEN=9;S1=6;REP=0 GT:PP:NP ./.:0:5)]; -const my $RES_ARR_DI => [2, 2, 0.002, 2, 8, 0.017, 0.714]; +const my $RES_ARR_DI => [qw(chr10 22777 id AGAAACTGTG ACTGTGAGATAGATATATATAGATAGATATAT 105 . PC=DI;RS=22777;RE=22787;LEN=9;S1=6;REP=0 GT:PP:NP ./.:0:5:2:2:0.002:2:8:0.017:0.714)]; const my $DATA_ARR_SI => [qw(chr10 11643 id C CG 150 . PC=I;RS=11643;RE=11649;LEN=1;S1=6;S2=421.908;REP=4 GT:PP:NP ./.:0:5)]; -const my $RES_ARR_SI => [7, 10, 0.014, 7, 10, 0.005, '0.500']; +const my $RES_ARR_SI => [qw(chr10 11643 id C CG 150 . PC=I;RS=11643;RE=11649;LEN=1;S1=6;S2=421.908;REP=4 GT:PP:NP ./.:0:5:7:10:0.014:7:10:0.005:0.500)]; my ($stdout_fh, $buffer); my ($sam_stdout_fh, $sam_buffer); @@ -60,23 +60,24 @@ subtest 'Header checks' => sub { subtest 'Simple Deletion checks' => sub { my $vba = new_vba(catfile($DATA, 'D.vcf')); - my $v_h = $vba->to_data_hash($DATA_ARR_D); - my $gt_out = $vba->blat_record($v_h); - is_deeply($gt_out->[0], $RES_ARR_D); + my @tmp = @{$DATA_ARR_D}; + $vba->blat_record(\@tmp); + is_deeply(\@tmp, $RES_ARR_D); }; subtest 'Simple Insertion checks' => sub { my $vba = new_vba(catfile($DATA, 'SI.vcf')); - my $v_h = $vba->to_data_hash($DATA_ARR_SI); - my $gt_out = $vba->blat_record($v_h); - is_deeply($gt_out->[0], $RES_ARR_SI); + my @tmp = @{$DATA_ARR_SI}; + $vba->blat_record(\@tmp); + is_deeply(\@tmp, $RES_ARR_SI); }; subtest 'Complex event checks' => sub { my $vba = new_vba(catfile($DATA, 'DI.vcf')); - my $v_h = $vba->to_data_hash($DATA_ARR_DI); - my $gt_out = $vba->blat_record($v_h); - is_deeply($gt_out->[0], $RES_ARR_DI); + my @tmp = @{$DATA_ARR_DI}; + $vba->blat_record(\@tmp); + print join q{ }, @tmp; + is_deeply(\@tmp, $RES_ARR_DI); }; done_testing(); @@ -90,7 +91,7 @@ sub new_vba { ref => catfile($DATA, 'chr10_1-23700.fa'), ofh => buffer_fh(), outpath => $tmp, - hts_file => [catfile($DATA, 'test.bam')], + hts_files => [catfile($DATA, 'test.bam')], ]); my $sample = $obj->{vcf_sample_order}->[0]; $obj->{sfh}->{$sample} = sam_buffer_fh(); From c2634138da5392d2de4dedaf7ab48df491915874 Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Thu, 13 Aug 2020 08:36:12 +0000 Subject: [PATCH 19/60] Minor updates to deploy --- .dockerignore | 1 + perl/Makefile.PL | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/.dockerignore b/.dockerignore index 613cb84..7e0be8f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -10,3 +10,4 @@ /pm_to_blib /perl/docs /perl/docs.tar.gz +/python/env diff --git a/perl/Makefile.PL b/perl/Makefile.PL index 988a5a6..8b51ed3 100755 --- a/perl/Makefile.PL +++ b/perl/Makefile.PL @@ -39,6 +39,10 @@ WriteMakefile( bin/pindelCohort_to_vcf.pl bin/pindel_vcfSortNsplit.pl bin/pindel_blat_vaf.pl + bin/pindelCohortMerge.pl + bin/pindelCohortVafFill.pl + bin/pindelCohortVafSplit.pl + bin/pindelCohortVafSliceFill.pl )], PREREQ_PM => { 'Const::Fast' => 0.014, From 0c385bb771d8aba66536a3c948163513c97eaeb7 Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Tue, 25 Aug 2020 12:26:49 +0000 Subject: [PATCH 20/60] Cleanup following bug hunt --- perl/bin/pindelCohortMerge.pl | 31 ++++++++----- perl/bin/pindelCohortVafFill.pl | 37 +++++++++++----- perl/bin/pindelCohortVafSliceFill.pl | 6 ++- perl/bin/pindelCohortVafSplit.pl | 13 +++--- perl/lib/Sanger/CGP/Pindel/Implement.pm | 44 +++++++++++++------ .../CGP/Pindel/OutputGen/VcfBlatAugment.pm | 13 +++--- 6 files changed, 98 insertions(+), 46 deletions(-) diff --git a/perl/bin/pindelCohortMerge.pl b/perl/bin/pindelCohortMerge.pl index d5118ba..4a96a11 100644 --- a/perl/bin/pindelCohortMerge.pl +++ b/perl/bin/pindelCohortMerge.pl @@ -26,10 +26,10 @@ # write stuff header($options->{output}, $options->{vcfs}, $sample_head); -records($options->{output}, $records, $sample_order, $options->{all}, $options->{np}, $options->{control}); +records($options->{output}, $records, $sample_order, $options->{min_vaf}, $options->{np}, $options->{control}); sub records { - my ($output, $records, $sample_order, $all, $np_tree, $control) = @_; + my ($output, $records, $sample_order, $min_vaf, $np_tree, $control) = @_; my %ds = %{$records}; my $uuid_gen = Data::UUID->new; my @samples = @{$sample_order}; @@ -56,25 +56,24 @@ sub records { my ($ref, $alt) = split ':', $seq_key; my $row = join "\t", $chr, $pos, $uuid_gen->to_string($uuid_gen->create), $ref, $alt, q{.}, q{}, $info, $format; - my $samples_with_vaf = 0; + my $samples_with_min_vaf = 0; for my $s(@samples) { if(exists $ds{$chr}{$pos}{$seq_key}{$s}) { $row .= "\t".$ds{$chr}{$pos}{$seq_key}{$s}; - $samples_with_vaf++ if($row !~ m/:0\.000$/); + my ($last_vaf) = $row =~ m/:([0-9.]+)$/; + $last_vaf = 0 if($last_vaf eq q{.}); + $samples_with_min_vaf++ if($last_vaf >= $min_vaf); } else { $row .= "\t."; } } - if($all || $samples_with_vaf > 0) { + if($samples_with_min_vaf > 0) { print $output $row."\n"; } } } } - #printf "Loci requiring fill-in: %d\n", $fill_in_loci; - #printf "Fill in required for: %d\n", $to_fill_in; - } sub collate_data { @@ -167,14 +166,15 @@ sub setup{ my %opts = ( 'cmd' => join(" ", $0, @ARGV), 'mnps' => 1, + 'min_vaf' => 0, ); GetOptions( 'h|help' => \$opts{h}, 'm|man' => \$opts{m}, 'v|version' => \$opts{v}, 'o|output=s' => \$opts{output}, 'n|np=s' => \$opts{np}, - 's|mnps=i' => \$opts{'mnps'}, - 'a|all' => \$opts{all}, + 's|mnps=i' => \$opts{mnps}, + 'k|min:f' => \$opts{min_vaf}, 'd|debug' => \$opts{debug}, 'c|control=s' => \$opts{control}, ); @@ -187,6 +187,14 @@ sub setup{ pod2usage(-verbose => 1) if(defined $opts{h}); pod2usage(-verbose => 2) if(defined $opts{m}); + if(defined $opts{min_vaf}) { + if($opts{min_vaf} < 0 || $opts{min_vaf} > 1) { + print STDERR "ERROR: -vaf is a fraction and must be between 0 and 1.\n"; + pod2usage(-verbose => 1); + } + $opts{min_vaf} = (sprintf '%.3f', $opts{min_vaf}) + 0; # force number to be stored + } + my @vcfs = @ARGV; $opts{vcfs} = \@vcfs; @@ -224,7 +232,8 @@ =head1 SYNOPSIS -output -o File path for VCF output (not compressed) Optional parameters: - -all -a Keep all events, even if VAF == 0/. for all samples + -min -k Keep events VAF >= VALUE (3dp) for 1 or more samples + - default is to retain events even if VAF == 0/. for all samples -np -n Normal panel gff3 file - ommit if no filtering required. -mnps -s Minimum normal panel samples required to exclude [default: >=1] -control -c Exclude any events where this sample has calls. diff --git a/perl/bin/pindelCohortVafFill.pl b/perl/bin/pindelCohortVafFill.pl index 5150bbb..1c8669e 100644 --- a/perl/bin/pindelCohortVafFill.pl +++ b/perl/bin/pindelCohortVafFill.pl @@ -36,16 +36,16 @@ } if(!exists $options->{'process'} || $options->{'process'} eq 'fill') { - for my $f(glob(catfile($options->{'split_dir'}, '*.vcf'))) { - push @{$options->{split_files}}, $f if($f =~ m{/\d+.vcf$}); + for my $f(glob(catfile($options->{'split_dir'}, '*.vcf.gz'))) { + push @{$options->{split_files}}, $f if($f =~ m{/\d+.vcf.gz$}); } $threads->add_function('fill', \&Sanger::CGP::Pindel::Implement::fill_split_vaf); - $threads->run(scalar @{$options->{split_files}}, 'fill', $options) + $threads->run(scalar @{$options->{split_files}}, 'fill', $options); } if(!exists $options->{'process'} || $options->{'process'} eq 'bams') { $threads->add_function('bams', \&Sanger::CGP::Pindel::Implement::merge_vaf_bams); - $threads->run(scalar @{$options->{primary_hts}}, 'bams', $options) + $threads->run(scalar @{$options->{primary_hts}}, 'bams', $options); } if(!exists $options->{'process'} || $options->{'process'} eq 'finalise') { @@ -78,6 +78,7 @@ sub setup { 'i|index:i' => \$opts{'index'}, 'l|limit:i' => \$opts{'limit'}, 'a|abort' => \$opts{'abort'}, + 'd|data=s' => \$opts{'data'}, ); if(defined $opts{v}) { @@ -108,26 +109,37 @@ sub setup { } PCAP::Cli::file_for_reading('input', $opts{input}); + PCAP::Cli::file_for_reading('data', $opts{data}); PCAP::Cli::file_for_reading('ref', $opts{ref}); - if(@ARGV < 2) { - die "Error: At least 2 sample.bam/cram datasets are requied." + if(@ARGV) { + die "ERROR: No positional arguments expected." } - my @hts_pairs = @ARGV; - for my $hts_pair(@hts_pairs) { - my ($primary, $secondary) = split /:/, $hts_pair; + open my $D, '<', $opts{data}; + while(my $l = <$D>) { + chomp $l; + my ($primary, $secondary); + if($l =~ m/^([^\t]+)\t([^\t]+)$/) { + ($primary, $secondary) = ($1, $2); + } + else { + die "ERROR: file '$opts{data}' is malformed\n"; + } PCAP::Cli::file_for_reading('bam/cram files', $primary); PCAP::Cli::file_for_reading('bam/cram files', $secondary); push @{$opts{primary_hts}}, $primary; push @{$opts{secondary_hts}}, $secondary; } + close $D; $opts{tmp} = catdir($opts{output}, 'tmpCohortVafFill'); make_path($opts{tmp}); $opts{split_dir} = catdir($opts{tmp}, 'split'); make_path($opts{split_dir}); + $opts{fill_dir} = catdir($opts{tmp}, 'fill'); + make_path($opts{fill_dir}); make_path(catdir($opts{tmp}, 'logs')); @@ -142,12 +154,17 @@ =head1 NAME =head1 SYNOPSIS -pindelCohortVafFill.pl [options] -i ... -o ... -r ... SAMPLE.bam:PINDEL.bam ... +pindelCohortVafFill.pl [options] -i ... -o ... -r ... -d ... Required parameters: -file -f VCF file to read in. -output -o Workspace directory and final output. -ref -r File path to the reference file used to provide the coordinate system. + -data -d File containing list of sequence data files for all samples used in "-file" + - format: tab separated BWA mapping followed by pindel_cohort reads, one sample per line. + + sample_A_bwa.bamsample_A_pindel.bam + sample_B_bwa.bamsample_B_pindel.bam Optional parameters: -name -n Stub name for final output files [$output/cohort...] diff --git a/perl/bin/pindelCohortVafSliceFill.pl b/perl/bin/pindelCohortVafSliceFill.pl index b0dc0ff..5bc7905 100644 --- a/perl/bin/pindelCohortVafSliceFill.pl +++ b/perl/bin/pindelCohortVafSliceFill.pl @@ -8,6 +8,7 @@ use FindBin qw($Bin); use lib "$Bin/../lib"; use Getopt::Long; +use IO::Compress::Gzip qw(:constants gzip $GzipError); use PCAP::Cli; use Sanger::CGP::Pindel::OutputGen::VcfBlatAugment; @@ -59,7 +60,8 @@ sub setup { $opts{'hts_files'} = \@htsfiles; $opts{outpath} = $opts{output}; - open my $ofh, '>', $opts{output}; + + my $ofh = new IO::Compress::Gzip $opts{output}, -Level => Z_BEST_SPEED or die "IO::Compress::Gzip failed: $GzipError\n"; $opts{output} = $ofh; return \%opts; @@ -81,7 +83,7 @@ =head1 SYNOPSIS Required parameters: -ref -r File path to the reference file used to provide the coordinate system. -input -i VCF file to read in. - -output -o File path for VCF output (not compressed), colcated sample bams + -output -o File path for VCF output (gz compressed), colcated sample bams Other: -help -h Brief help message. diff --git a/perl/bin/pindelCohortVafSplit.pl b/perl/bin/pindelCohortVafSplit.pl index 83fc606..2a6dac7 100755 --- a/perl/bin/pindelCohortVafSplit.pl +++ b/perl/bin/pindelCohortVafSplit.pl @@ -11,6 +11,7 @@ use File::Path qw(make_path); use File::Spec::Functions; use IO::Uncompress::Gunzip qw(gunzip $GunzipError); +use IO::Compress::Gzip qw(:constants gzip $GzipError); use PCAP::Cli; { @@ -21,15 +22,15 @@ sub split_data { my ($input, $outdir, $max_e) = @_; make_path($outdir); - my $part_fmt = '%s/%04d.vcf'; + my $part_fmt = '%s/%04d.vcf.gz'; my @header; my $no_vaf_count = 0; my $no_vaf_file_no = 0; - my $complete_rec = catfile($outdir, 'complete_rec.vcf'); + my $complete_rec = catfile($outdir, 'complete_rec.vaf.vcf.gz'); my $no_vaf_file = sprintf $part_fmt, $outdir, $no_vaf_file_no++; - open my $COMP, '>', $complete_rec; + my $COMP = new IO::Compress::Gzip $complete_rec, -Level => Z_BEST_SPEED or die "IO::Compress::Gzip failed: $GzipError\n"; my $NO_VAF; my $z = IO::Uncompress::Gunzip->new($input, MultiStream => 1) or die "gunzip failed: $GunzipError\n"; @@ -41,7 +42,8 @@ sub split_data { } unless($NO_VAF) { warn "Creating $no_vaf_file\n"; - open $NO_VAF, '>', $no_vaf_file; + $NO_VAF = new IO::Compress::Gzip $no_vaf_file, -Level => Z_BEST_SPEED or die "IO::Compress::Gzip failed: $GzipError\n"; + #open $NO_VAF, '>', $no_vaf_file; print $NO_VAF join q{}, @header; } chomp $line; @@ -56,7 +58,8 @@ sub split_data { close $NO_VAF; $no_vaf_file = sprintf $part_fmt, $outdir, $no_vaf_file_no++; warn "\nCreating $no_vaf_file\n"; - open $NO_VAF, '>', $no_vaf_file; + $NO_VAF = new IO::Compress::Gzip $no_vaf_file, -Level => Z_BEST_SPEED or die "IO::Compress::Gzip failed: $GzipError\n"; + #open $NO_VAF, '>', $no_vaf_file; print $NO_VAF join q{}, @header; $no_vaf_count = 0; } diff --git a/perl/lib/Sanger/CGP/Pindel/Implement.pm b/perl/lib/Sanger/CGP/Pindel/Implement.pm index 7f558ff..2d3d218 100644 --- a/perl/lib/Sanger/CGP/Pindel/Implement.pm +++ b/perl/lib/Sanger/CGP/Pindel/Implement.pm @@ -264,7 +264,7 @@ sub concat { # now deal with the sam files unless(PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), 'calmd')) { my $samtools = _which('samtools'); - my $command = sprintf q{(%s view -H %s | grep -P '^@(HD|SQ)' && grep -vP '^@(HD|SQ)' %s | sort | uniq) | %s sort -l 0 -T %s - | %s calmd -b - %s > %s}, + my $command = sprintf q{(%s view -H %s | grep -P '^@(HD|SQ)' && grep -hvP '^@(HD|SQ)' %s | sort | uniq) | %s sort -l 0 -T %s - | %s calmd -b - %s > %s}, $samtools, $hts_input, File::Spec->catfile($vcf, sprintf 'blat_*.%s.sam', $sample_name), $samtools, File::Spec->catfile($vcf, 'srt'), @@ -736,9 +736,9 @@ sub cohort_split { sub fill_split_vaf { my ($index_in, $options) = @_; - my $tmp = $options->{'tmp'}; + my $tmp = $options->{tmp}; - return 1 if(exists $options->{'index'} && $index_in != $options->{'index'}); + return 1 if(exists $options->{index} && $index_in != $options->{index}); my @split_files = @{$options->{split_files}}; @@ -746,7 +746,8 @@ sub fill_split_vaf { for my $index(@indicies) { next if PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), $index); my $split_file = $split_files[$index-1]; - my $fill_file = $split_file =~ s/vcf$/vaf.vcf/r; + my $fill_basename = fileparse($split_file); + my $fill_file = catfile($options->{fill_dir}, $fill_basename); my $command = $^X.' '._which('pindelCohortVafSliceFill.pl'); $command .= sprintf ' -r %s -i %s -o %s ', $options->{ref}, $split_file, $fill_file; $command .= join q{ }, @{$options->{primary_hts}}; @@ -766,33 +767,50 @@ sub merge_vaf_bams { next if PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), $index); my $arr_idx = $index-1; my $sample = sanitised_sample_from_bam($options->{primary_hts}->[$arr_idx]); + my $sort_tmp = catdir($options->{'tmp'}, sprintf 'sort_tmp_%d', $index); + make_path($sort_tmp); + my $in_file_list = catfile($sort_tmp, 'merge_files'); my @split_bams; - for my $f(glob(catfile($options->{'split_dir'}, sprintf '*.vaf.%s.bam', $sample))) { - push @split_bams, $f if($f =~ m{/\d+\.vaf.$sample\.bam$}); + for my $f(glob(catfile($options->{'fill_dir'}, sprintf '*.vcf.gz.%s.bam', $sample))) { + push @split_bams, $f if($f =~ m{/\d+\.vcf.gz.$sample\.bam$}); } + # list of bams to merge + lines_to_file($in_file_list, [$options->{secondary_hts}->[$arr_idx], @split_bams]); my $command = _which('samtools'); # needs to attempt to clean up duplicate reads - $command .= sprintf q{ merge --output-fmt SAM -nf - %s | pee 'grep ^@' 'grep -v ^@ | sort | uniq' | samtools view -u - | samtools sort -o %s -}, - join( q{ }, $options->{secondary_hts}->[$arr_idx], @split_bams), - catfile($options->{output}, sprintf('%s.vaf.bam', $sample)); + $command .= sprintf q{ merge --output-fmt SAM -b %s - | pee 'grep ^@' 'grep -v ^@ | sort -S 2G -T %s | uniq' | samtools view -u - | samtools sort -m 2G -T %s -o %s -}, + $in_file_list, # merge filelist + $sort_tmp, # sort tmptfile + catfile($sort_tmp, 'samsort'), + catfile($options->{output}, sprintf('%s.vaf.bam', $sample)); # outfile PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), $command, $index); + remove_tree($sort_tmp); PCAP::Threaded::touch_success(File::Spec->catdir($tmp, 'progress'), $index); } } +sub lines_to_file { + my($file_name, $list_of_str) = @_; + open my $fh, '>', $file_name; + print $fh join "\n", @{$list_of_str}; + print $fh "\n"; # to make valid file + close $fh; +} + sub fill_vcf_merge { my $options = shift; my $tmp = $options->{'tmp'}; return if PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), 0); - my @split_bams; - for my $f(glob(catfile($options->{'split_dir'}, sprintf '*.vaf.vcf'))) { - push @split_bams, $f if($f =~ m{/\d+\.vaf\.vcf$}); + my @split_vcf; + for my $f(glob(catfile($options->{fill_dir}, sprintf '.vcf'))) { + push @split_vcf, $f if($f =~ m{/\d+\.vaf\.vcf$}); } + push @split_vcf, catfile($options->{split_dir}, 'complete_rec.vaf.vcf.gz'); my $final_vcf = catfile($options->{output}, sprintf '%s.vaf.vcf.gz', $options->{name}); my $merge = sprintf q{(grep '^#' %s ; grep -vh '^#' %s | sort -k1,1 -k2,2n -k 4,4 -k5,5) | bgzip -c > %s}, - $split_bams[0], join(q{ } , @split_bams), + $split_vcf[0], join(q{ } , @split_vcf), $final_vcf; my $tabix = sprintf q{tabix -fp vcf %s}, $final_vcf; diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm index 206ce38..44181c7 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm @@ -29,6 +29,7 @@ use File::Basename; use List::Util qw(min max); use File::Temp qw(tempfile); use Capture::Tiny qw(capture); +use IO::Compress::Gzip qw(:constants gzip $GzipError); use Vcf; use Bio::DB::HTS; use Bio::DB::HTS::Faidx; @@ -108,11 +109,12 @@ sub _align_output { $self->_fa_dict(); $outpath =~ s/\.vcf$//; for my $sample(@{$self->{vcf_sample_order}}) { - my $sam_file = sprintf '%s.%s.sam', $outpath, $sample; + my $sam_file = sprintf '%s.%s.sam.gz', $outpath, $sample; $self->{samfile}->{$sample} = $sam_file; $self->{bamfile}->{$sample} = sprintf '%s.%s.bam', $outpath, $sample; unlink $sam_file if(-e $sam_file); - open my $SAM, '>', $sam_file; + + my $SAM = new IO::Compress::Gzip $sam_file, -Level => Z_BEST_SPEED or die "IO::Compress::Gzip failed: $GzipError\n"; print $SAM join "\n", @{$self->{fa_dict}}; print $SAM "\n"; $self->{sfh}->{$sample} = $SAM; @@ -321,7 +323,7 @@ sub blat_record { } my $gt_set = $self->blat_reads($v_h, $file_target, $sample); if($v_d->[$gt_pos] eq q{.}) { - $v_d->[$gt_pos] = join q{:}, './.:.:.', @{$gt_set}; + $v_d->[$gt_pos] = join q{:}, './.:.:.:.:.', @{$gt_set}; } else { $v_d->[$gt_pos] = join q{:}, $v_d->[$gt_pos], @{$gt_set}; @@ -521,8 +523,9 @@ sub sam_to_bam { my $bam = $self->{bamfile}->{$sample}; my $tmp = $bam; $tmp =~ s/bam$/tmp/; - my $command = sprintf q{bash -c 'set -o pipefail ; samtools view -uT %s %s | samtools sort -l 0 -T %s - | samtools calmd - %s > %s'}, - $self->{ref}, $sam, # view + my $command = sprintf q{bash -c 'set -o pipefail ; zcat %s | samtools view -uT %s - | samtools sort -l 0 -T %s - | samtools calmd - %s > %s'}, + $sam, # zcat + $self->{ref}, # view $tmp, # sort $self->{'ref'}, $bam; # calmd my ($c_out, $c_err, $c_exit) = capture { system($command); }; From 11d1ed0bc2d5cc58a6b5574418e944fb445013e9 Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Wed, 26 Aug 2020 14:24:00 +0000 Subject: [PATCH 21/60] Final few issues with change to compressed files handled. --- perl/bin/pindelCohort.pl | 9 ++---- perl/bin/pindelCohortVafFill.pl | 43 +++++++++++++++++-------- perl/bin/pindelCohortVafSliceFill.pl | 18 +++++++---- perl/lib/Sanger/CGP/Pindel/Implement.pm | 19 ++++++----- 4 files changed, 53 insertions(+), 36 deletions(-) diff --git a/perl/bin/pindelCohort.pl b/perl/bin/pindelCohort.pl index 155a709..a58ec17 100644 --- a/perl/bin/pindelCohort.pl +++ b/perl/bin/pindelCohort.pl @@ -51,13 +51,6 @@ BEGIN { my $options = setup(); my $threads = PCAP::Threaded->new($options->{'threads'}); - #&PCAP::Threaded::disable_out_err if(exists $options->{'index'}); - - # register any process that can run in parallel here - $threads->add_function('input', \&Sanger::CGP::Pindel::Implement::input_cohort); - $threads->add_function('pindel', \&Sanger::CGP::Pindel::Implement::pindel); - $threads->add_function('blat', \&Sanger::CGP::Pindel::Implement::blat); - # start processes here (in correct order obviously), add conditions for skipping based on 'process' option if(!exists $options->{'process'} || $options->{'process'} eq 'input') { @@ -66,6 +59,7 @@ BEGIN if(!exists $options->{'process'} || $options->{'process'} eq 'pindel') { my $jobs = Sanger::CGP::Pindel::Implement::determine_jobs($options); # method still needed to populate info $jobs = $options->{'limit'} if(exists $options->{'limit'} && defined $options->{'limit'}); + $threads->add_function('pindel', \&Sanger::CGP::Pindel::Implement::pindel); $threads->run($jobs, 'pindel', $options); } if(!exists $options->{'process'} || $options->{'process'} eq 'parse') { @@ -75,6 +69,7 @@ BEGIN if(!exists $options->{'process'} || $options->{'process'} eq 'blat') { my $jobs = scalar @{$options->{'split_files'}}; $jobs = $options->{'limit'} if(exists $options->{'limit'} && defined $options->{'limit'}); + $threads->add_function('blat', \&Sanger::CGP::Pindel::Implement::blat); $threads->run($jobs, 'blat', $options); } if(!exists $options->{'process'} || $options->{'process'} eq 'concat') { diff --git a/perl/bin/pindelCohortVafFill.pl b/perl/bin/pindelCohortVafFill.pl index 1c8669e..3cf407e 100644 --- a/perl/bin/pindelCohortVafFill.pl +++ b/perl/bin/pindelCohortVafFill.pl @@ -29,13 +29,14 @@ { my $options = setup(); - my $threads = PCAP::Threaded->new($options->{'threads'}); + my $threads = PCAP::Threaded->new($options->{threads}); - if(!exists $options->{'process'} || $options->{'process'} eq 'split') { + if(!exists $options->{process} || $options->{process} eq 'split') { Sanger::CGP::Pindel::Implement::cohort_split($options); + vaf_fill_seqdata($options); } - if(!exists $options->{'process'} || $options->{'process'} eq 'fill') { + if(!exists $options->{process} || $options->{process} eq 'fill') { for my $f(glob(catfile($options->{'split_dir'}, '*.vcf.gz'))) { push @{$options->{split_files}}, $f if($f =~ m{/\d+.vcf.gz$}); } @@ -43,20 +44,33 @@ $threads->run(scalar @{$options->{split_files}}, 'fill', $options); } - if(!exists $options->{'process'} || $options->{'process'} eq 'bams') { + if(!exists $options->{process} || $options->{process} eq 'bams') { $threads->add_function('bams', \&Sanger::CGP::Pindel::Implement::merge_vaf_bams); $threads->run(scalar @{$options->{primary_hts}}, 'bams', $options); } - if(!exists $options->{'process'} || $options->{'process'} eq 'finalise') { + if(!exists $options->{process} || $options->{process} eq 'finalise') { Sanger::CGP::Pindel::Implement::fill_vcf_merge($options); - move(catdir($options->{tmp}, 'logs'), catdir($options->{output}, 'logs')); - remove_tree($options->{tmp}); + if(!$options->{debug}) { + move(catdir($options->{tmp}, 'logs'), catdir($options->{output}, 'logs')); + remove_tree($options->{tmp}); + } } } +sub vaf_fill_seqdata { + my ($options) = @_; + my $bwa_files = $options->{bwa_file_list}; + return if (-e $bwa_files); + open my $FH, '>', $bwa_files; + for my $f(@{$options->{primary_hts}}) { + print $FH qq{$f\n}; + } + close $FH; +} + sub setup { my %opts = ( 'size' => 10000, @@ -73,12 +87,13 @@ sub setup { 'o|output=s' => \$opts{output}, 's|size:i' => \$opts{size}, 'n|name:s' => \$opts{name}, - 'c|cpus:i' => \$opts{'threads'}, - 'p|process:s' => \$opts{'process'}, - 'i|index:i' => \$opts{'index'}, - 'l|limit:i' => \$opts{'limit'}, - 'a|abort' => \$opts{'abort'}, - 'd|data=s' => \$opts{'data'}, + 'c|cpus:i' => \$opts{threads}, + 'p|process:s' => \$opts{process}, + 'i|index:i' => \$opts{index}, + 'l|limit:i' => \$opts{limit}, + 'a|abort' => \$opts{abort}, + 'd|data=s' => \$opts{data}, + 'debug' => \$opts{debug} ); if(defined $opts{v}) { @@ -140,6 +155,7 @@ sub setup { make_path($opts{split_dir}); $opts{fill_dir} = catdir($opts{tmp}, 'fill'); make_path($opts{fill_dir}); + $opts{bwa_file_list} = catfile($opts{tmp}, 'bwa_files.lst'); make_path(catdir($opts{tmp}, 'logs')); @@ -184,6 +200,7 @@ =head1 SYNOPSIS -help -h Brief help message. -man -m Full documentation. -version -v Prints the version number. + -debug Don't cleanup anything =head1 DESCRIPTION diff --git a/perl/bin/pindelCohortVafSliceFill.pl b/perl/bin/pindelCohortVafSliceFill.pl index 5bc7905..ae5f3c8 100644 --- a/perl/bin/pindelCohortVafSliceFill.pl +++ b/perl/bin/pindelCohortVafSliceFill.pl @@ -39,6 +39,7 @@ sub setup { 'i|input=s' => \$opts{input}, 'r|ref=s' => \$opts{ref}, 'o|output=s' => \$opts{output}, + 'd|data=s' => \$opts{data}, ); if(defined $opts{v}) { @@ -53,10 +54,15 @@ sub setup { $opts{align} = $opts{output}.'.fill.sam' unless(defined $opts{align}); - my @htsfiles = @ARGV; - for my $hts(@htsfiles) { - PCAP::Cli::file_for_reading('bam/cram files', $hts); + my @htsfiles; + open my $D, '<', $opts{data}; + while(my $hts_file = <$D>) { + chomp $hts_file; + PCAP::Cli::file_for_reading('bam/cram files', $hts_file); + push @htsfiles, $hts_file; } + close $D; + $opts{'hts_files'} = \@htsfiles; $opts{outpath} = $opts{output}; @@ -76,14 +82,14 @@ =head1 NAME =head1 SYNOPSIS -pindelCohortVafSliceFill.pl [options] SAMPLE_1.bam SAMPLE_2.bam [...] - - SAMPLE*.bam should have co-located *.bai and *.bas files. +pindelCohortVafSliceFill.pl [options] Required parameters: -ref -r File path to the reference file used to provide the coordinate system. -input -i VCF file to read in. -output -o File path for VCF output (gz compressed), colcated sample bams + -data -d File containing list of BWA mappingfiles for all samples used in "-input" + - format: one BWA bam/cram file per line, expects co-located *.bai Other: -help -h Brief help message. diff --git a/perl/lib/Sanger/CGP/Pindel/Implement.pm b/perl/lib/Sanger/CGP/Pindel/Implement.pm index 2d3d218..d6302e8 100644 --- a/perl/lib/Sanger/CGP/Pindel/Implement.pm +++ b/perl/lib/Sanger/CGP/Pindel/Implement.pm @@ -264,9 +264,9 @@ sub concat { # now deal with the sam files unless(PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), 'calmd')) { my $samtools = _which('samtools'); - my $command = sprintf q{(%s view -H %s | grep -P '^@(HD|SQ)' && grep -hvP '^@(HD|SQ)' %s | sort | uniq) | %s sort -l 0 -T %s - | %s calmd -b - %s > %s}, + my $command = sprintf q{(%s view -H %s | grep -P '^@(HD|SQ)' && zgrep -hvP '^@(HD|SQ)' %s | sort | uniq) | %s sort -l 0 -T %s - | %s calmd -b - %s > %s}, $samtools, $hts_input, - File::Spec->catfile($vcf, sprintf 'blat_*.%s.sam', $sample_name), + File::Spec->catfile($vcf, sprintf 'blat_*.%s.sam.gz', $sample_name), $samtools, File::Spec->catfile($vcf, 'srt'), $samtools, $options->{'reference'}, $bam; PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), ['set -o pipefail', $command], 'calmd'); @@ -749,8 +749,7 @@ sub fill_split_vaf { my $fill_basename = fileparse($split_file); my $fill_file = catfile($options->{fill_dir}, $fill_basename); my $command = $^X.' '._which('pindelCohortVafSliceFill.pl'); - $command .= sprintf ' -r %s -i %s -o %s ', $options->{ref}, $split_file, $fill_file; - $command .= join q{ }, @{$options->{primary_hts}}; + $command .= sprintf ' -r %s -i %s -o %s -d %s', $options->{ref}, $split_file, $fill_file, $options->{bwa_file_list}; PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), $command, $index); PCAP::Threaded::touch_success(File::Spec->catdir($tmp, 'progress'), $index); @@ -803,14 +802,14 @@ sub fill_vcf_merge { my $options = shift; my $tmp = $options->{'tmp'}; return if PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), 0); - my @split_vcf; - for my $f(glob(catfile($options->{fill_dir}, sprintf '.vcf'))) { - push @split_vcf, $f if($f =~ m{/\d+\.vaf\.vcf$}); + my @split_filled_vcf; + for my $f(glob(catfile($options->{fill_dir}, '*.vcf.gz'))) { + push @split_filled_vcf, $f if($f =~ m{/\d+\.vcf.gz$}); } - push @split_vcf, catfile($options->{split_dir}, 'complete_rec.vaf.vcf.gz'); + my $complete_recs = catfile($options->{split_dir}, 'complete_rec.vaf.vcf.gz'); my $final_vcf = catfile($options->{output}, sprintf '%s.vaf.vcf.gz', $options->{name}); - my $merge = sprintf q{(grep '^#' %s ; grep -vh '^#' %s | sort -k1,1 -k2,2n -k 4,4 -k5,5) | bgzip -c > %s}, - $split_vcf[0], join(q{ } , @split_vcf), + my $merge = sprintf q{(zgrep '^#' %s ; zgrep -vh '^#' %s | sort -k1,1 -k2,2n -k 4,4 -k5,5) | bgzip -c > %s}, + $complete_recs, join(q{ } , @split_filled_vcf, $complete_recs), $final_vcf; my $tabix = sprintf q{tabix -fp vcf %s}, $final_vcf; From c3edc4949ec26dae120302214a974a8155b19208 Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Fri, 4 Sep 2020 10:05:55 +0000 Subject: [PATCH 22/60] reduce change of exceptionally high number of files in a single directory --- perl/bin/pindelCohortVafFill.pl | 2 - perl/bin/pindelCohortVafSliceFill.pl | 9 +- perl/lib/Sanger/CGP/Pindel/Implement.pm | 187 +++++++++--------- .../CGP/Pindel/OutputGen/VcfBlatAugment.pm | 6 +- perl/t/data/blat/{D.vcf => D/test.vcf} | 0 perl/t/data/blat/{DI.vcf => DI/test.vcf} | 0 perl/t/data/blat/{SI.vcf => SI/test.vcf} | 0 perl/t/vcfBlatAugment.t | 16 +- 8 files changed, 111 insertions(+), 109 deletions(-) rename perl/t/data/blat/{D.vcf => D/test.vcf} (100%) rename perl/t/data/blat/{DI.vcf => DI/test.vcf} (100%) rename perl/t/data/blat/{SI.vcf => SI/test.vcf} (100%) diff --git a/perl/bin/pindelCohortVafFill.pl b/perl/bin/pindelCohortVafFill.pl index 3cf407e..9eb3a43 100644 --- a/perl/bin/pindelCohortVafFill.pl +++ b/perl/bin/pindelCohortVafFill.pl @@ -56,8 +56,6 @@ remove_tree($options->{tmp}); } } - - } sub vaf_fill_seqdata { diff --git a/perl/bin/pindelCohortVafSliceFill.pl b/perl/bin/pindelCohortVafSliceFill.pl index ae5f3c8..ccadc35 100644 --- a/perl/bin/pindelCohortVafSliceFill.pl +++ b/perl/bin/pindelCohortVafSliceFill.pl @@ -8,6 +8,8 @@ use FindBin qw($Bin); use lib "$Bin/../lib"; use Getopt::Long; +use File::Spec::Functions; +use File::Path qw(make_path); use IO::Compress::Gzip qw(:constants gzip $GzipError); use PCAP::Cli; @@ -63,11 +65,12 @@ sub setup { } close $D; - $opts{'hts_files'} = \@htsfiles; + $opts{hts_files} = \@htsfiles; $opts{outpath} = $opts{output}; + make_path($opts{outpath}) unless(-e $opts{outpath}); - my $ofh = new IO::Compress::Gzip $opts{output}, -Level => Z_BEST_SPEED or die "IO::Compress::Gzip failed: $GzipError\n"; + my $ofh = new IO::Compress::Gzip catfile($opts{output}, 'slice.vcf.gz'), -Level => Z_BEST_SPEED or die "IO::Compress::Gzip failed: $GzipError\n"; $opts{output} = $ofh; return \%opts; @@ -87,7 +90,7 @@ =head1 SYNOPSIS Required parameters: -ref -r File path to the reference file used to provide the coordinate system. -input -i VCF file to read in. - -output -o File path for VCF output (gz compressed), colcated sample bams + -output -o Directory for VCF output (gz compressed) and colocated sample bams -data -d File containing list of BWA mappingfiles for all samples used in "-input" - format: one BWA bam/cram file per line, expects co-located *.bai diff --git a/perl/lib/Sanger/CGP/Pindel/Implement.pm b/perl/lib/Sanger/CGP/Pindel/Implement.pm index d6302e8..417a624 100644 --- a/perl/lib/Sanger/CGP/Pindel/Implement.pm +++ b/perl/lib/Sanger/CGP/Pindel/Implement.pm @@ -27,7 +27,7 @@ use warnings FATAL => 'all'; use autodie qw(:all); use Cwd qw(cwd); use Const::Fast qw(const); -use File::Spec; +use File::Spec::Functions; use File::Which qw(which); use File::Copy qw(copy); use File::Path qw(make_path remove_tree); @@ -38,7 +38,6 @@ use FindBin qw($Bin); use Getopt::Long; use Pod::Usage qw(pod2usage); use File::Basename; -use File::Spec::Functions; use Sanger::CGP::Pindel; @@ -62,13 +61,13 @@ sub input_cohort{ my ($options) = @_; my $tmp = $options->{'tmp'}; - return 1 if PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), 0); + return 1 if PCAP::Threaded::success_exists(catdir($tmp, 'progress'), 0); my $input = $options->{'hts_files'}->[0]; my $max_threads = $options->{'threads'}; my $sample = sanitised_sample_from_bam($input); - my $gen_out = File::Spec->catdir($tmp, $sample); + my $gen_out = catdir($tmp, $sample); make_path($gen_out) unless(-e $gen_out); my $command = "$^X "; @@ -77,8 +76,8 @@ sub input_cohort{ $command .= " -r $options->{reference}"; $command .= " -e $options->{badloci}" if(exists $options->{'badloci'}); - PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), $command, 0); - PCAP::Threaded::touch_success(File::Spec->catdir($tmp, 'progress'), 0); + PCAP::Threaded::external_process_handler(catdir($tmp, 'logs'), $command, 0); + PCAP::Threaded::touch_success(catdir($tmp, 'progress'), 0); return 1; } @@ -87,7 +86,7 @@ sub input { return 1 if(exists $options->{'index'} && $index != $options->{'index'}); my $tmp = $options->{'tmp'}; - return 1 if PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), $index); + return 1 if PCAP::Threaded::success_exists(catdir($tmp, 'progress'), $index); my @inputs = ($options->{'tumour'}, $options->{'normal'}); my $iter = 1; @@ -104,7 +103,7 @@ sub input { $max_threads = 1 if($max_threads == 0); my $sample = sanitised_sample_from_bam($input); - my $gen_out = File::Spec->catdir($tmp, $sample); + my $gen_out = catdir($tmp, $sample); make_path($gen_out) unless(-e $gen_out); my $command = "$^X "; @@ -113,11 +112,11 @@ sub input { $command .= " -r $options->{reference}"; $command .= " -e $options->{badloci}" if(exists $options->{'badloci'}); - PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), $command, $index); + PCAP::Threaded::external_process_handler(catdir($tmp, 'logs'), $command, $index); # ## The rest is auto-magical - PCAP::Threaded::touch_success(File::Spec->catdir($tmp, 'progress'), $index); + PCAP::Threaded::touch_success(catdir($tmp, 'progress'), $index); } return 1; } @@ -131,7 +130,7 @@ sub pindel { my @seqs = sort keys %{$options->{'seqs'}}; my @indicies = limited_indicies($options, $index_in, scalar @seqs); for my $index(@indicies) { - next if PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), $index); + next if PCAP::Threaded::success_exists(catdir($tmp, 'progress'), $index); my $seq = $seqs[$index-1]; ## build commands for this index @@ -140,10 +139,10 @@ sub pindel { my @command_set; # was split - my $refs = File::Spec->catdir($tmp, 'refs'); + my $refs = catdir($tmp, 'refs'); make_path($refs) unless(-e $refs); - my $refseq_file = File::Spec->catfile($refs, "$seq.fa"); + my $refseq_file = catfile($refs, "$seq.fa"); my $split_comm = _which('samtools'); $split_comm .= sprintf $SAMTOOLS_FAIDX, $options->{'reference'}, @@ -153,16 +152,16 @@ sub pindel { push @command_set, $split_comm; # was filter - my $filter_out = File::Spec->catdir($tmp, 'filter'); + my $filter_out = catdir($tmp, 'filter'); make_path($filter_out) unless(-e $filter_out); - my $filtered_seq = File::Spec->catfile($filter_out, $seq); + my $filtered_seq = catfile($filter_out, $seq); # pindel - my $gen_out = File::Spec->catdir($tmp, 'pout'); + my $gen_out = catdir($tmp, 'pout'); make_path($gen_out) unless(-e $gen_out); - my ($bd_fh, $bd_file) = tempfile(File::Spec->catfile($tmp, 'pindel_db_XXXX'), UNLINK => 0); + my ($bd_fh, $bd_file) = tempfile(catfile($tmp, 'pindel_db_XXXX'), UNLINK => 0); close $bd_fh; unlink $filtered_seq if(-e $filtered_seq); @@ -182,11 +181,11 @@ sub pindel { $bd_file, 5; - PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), \@command_set, $index); + PCAP::Threaded::external_process_handler(catdir($tmp, 'logs'), \@command_set, $index); # a little cleanup for my $ext((qw(BP INV LI TD))) { - unlink File::Spec->catfile($gen_out, (join '_', $seq, $seq, $ext)); + unlink catfile($gen_out, (join '_', $seq, $seq, $ext)); } unlink $bd_file; unlink $refseq_file; @@ -195,7 +194,7 @@ sub pindel { # ## The rest is auto-magical - PCAP::Threaded::touch_success(File::Spec->catdir($tmp, 'progress'), $index); + PCAP::Threaded::touch_success(catdir($tmp, 'progress'), $index); } return 1; } @@ -204,12 +203,12 @@ sub parse { my ($options) = @_; my $tmp = $options->{'tmp'}; - my $pout = File::Spec->catdir($tmp, 'pout'); - my $vcf = File::Spec->catdir($tmp, 'vcf'); + my $pout = catdir($tmp, 'pout'); + my $vcf = catdir($tmp, 'vcf'); make_path($vcf) unless(-e $vcf); - my $collated_vcf = File::Spec->catfile($vcf, 'raw.vcf'); + my $collated_vcf = catfile($vcf, 'raw.vcf'); - unless(PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), 'tovcf')) { + unless(PCAP::Threaded::success_exists(catdir($tmp, 'progress'), 'tovcf')) { my $bad_loci = q{}; if($options->{badloci}) { $bad_loci = sprintf q{ -b %s }, $options->{badloci}; @@ -217,22 +216,22 @@ sub parse { my $command = $^X.' '._which('pindelCohort_to_vcf.pl'); $command .= sprintf $COHORT_2_VCF, $options->{'reference'}, - File::Spec->catfile($pout, '%_%_%'), + catfile($pout, '%_%_%'), $collated_vcf, $bad_loci, join(q{ }, @{$options->{hts_files}}); - PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), $command, 'tovcf'); - PCAP::Threaded::touch_success(File::Spec->catdir($tmp, 'progress'), 'tovcf'); + PCAP::Threaded::external_process_handler(catdir($tmp, 'logs'), $command, 'tovcf'); + PCAP::Threaded::touch_success(catdir($tmp, 'progress'), 'tovcf'); } - unless(PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), 'split')) { + unless(PCAP::Threaded::success_exists(catdir($tmp, 'progress'), 'split')) { #perl pindel_vcfSortNsplit.pl run_PD26988a/tmpPindel/vcf/raw.vcf 10000 tsrt my $command = $^X.' '._which('pindel_vcfSortNsplit.pl'); $command .= sprintf q{ %s %d %s}, $collated_vcf, $VCF_SPLIT_SIZE, $vcf; - PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), $command, 'split'); - PCAP::Threaded::touch_success(File::Spec->catdir($tmp, 'progress'), 'split'); + PCAP::Threaded::external_process_handler(catdir($tmp, 'logs'), $command, 'split'); + PCAP::Threaded::touch_success(catdir($tmp, 'progress'), 'split'); } return 1; } @@ -241,49 +240,49 @@ sub concat { my ($options) = @_; my $tmp = $options->{'tmp'}; - my $vcf = File::Spec->catdir($tmp, 'vcf'); + my $vcf = catdir($tmp, 'vcf'); my $hts_input = $options->{'hts_files'}->[0]; my $sample_name = (PCAP::Bam::sample_name($hts_input))[0]; - my $vcf_gz = File::Spec->catfile($options->{'outdir'}, sprintf('%s.pindel.vcf.gz', $sample_name)); - my $bam = File::Spec->catfile($options->{'outdir'}, sprintf('%s.pindel.bam', $sample_name)); - unless(PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), 'concat')) { + my $vcf_gz = catfile($options->{'outdir'}, sprintf('%s.pindel.vcf.gz', $sample_name)); + my $bam = catfile($options->{'outdir'}, sprintf('%s.pindel.bam', $sample_name)); + unless(PCAP::Threaded::success_exists(catdir($tmp, 'progress'), 'concat')) { #vcf-concat blat_*.vcf my $command = _which('vcf-concat'); $command .= sprintf q{ %s | bgzip -c > %s}, - File::Spec->catfile($vcf, 'blat_*.vcf'), + catfile($vcf, 'blat_*.vcf'), $vcf_gz; - PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), ['set -o pipefail', $command], 'concat'); - PCAP::Threaded::touch_success(File::Spec->catdir($tmp, 'progress'), 'concat'); + PCAP::Threaded::external_process_handler(catdir($tmp, 'logs'), ['set -o pipefail', $command], 'concat'); + PCAP::Threaded::touch_success(catdir($tmp, 'progress'), 'concat'); } - unless(PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), 'tabix')) { + unless(PCAP::Threaded::success_exists(catdir($tmp, 'progress'), 'tabix')) { my $command = _which('tabix'); $command .= sprintf q{ -f -p vcf %s}, $vcf_gz; - PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), $command, 'tabix'); - PCAP::Threaded::touch_success(File::Spec->catdir($tmp, 'progress'), 'tabix'); + PCAP::Threaded::external_process_handler(catdir($tmp, 'logs'), $command, 'tabix'); + PCAP::Threaded::touch_success(catdir($tmp, 'progress'), 'tabix'); } # now deal with the sam files - unless(PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), 'calmd')) { + unless(PCAP::Threaded::success_exists(catdir($tmp, 'progress'), 'calmd')) { my $samtools = _which('samtools'); my $command = sprintf q{(%s view -H %s | grep -P '^@(HD|SQ)' && zgrep -hvP '^@(HD|SQ)' %s | sort | uniq) | %s sort -l 0 -T %s - | %s calmd -b - %s > %s}, $samtools, $hts_input, - File::Spec->catfile($vcf, sprintf 'blat_*.%s.sam.gz', $sample_name), - $samtools, File::Spec->catfile($vcf, 'srt'), + catfile($vcf, sprintf 'blat_*.%s.sam.gz', $sample_name), + $samtools, catfile($vcf, 'srt'), $samtools, $options->{'reference'}, $bam; - PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), ['set -o pipefail', $command], 'calmd'); - PCAP::Threaded::touch_success(File::Spec->catdir($tmp, 'progress'), 'calmd'); + PCAP::Threaded::external_process_handler(catdir($tmp, 'logs'), ['set -o pipefail', $command], 'calmd'); + PCAP::Threaded::touch_success(catdir($tmp, 'progress'), 'calmd'); } - unless(PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), 'index')) { + unless(PCAP::Threaded::success_exists(catdir($tmp, 'progress'), 'index')) { my $command = _which('samtools'); $command .= sprintf q{ index %s}, $bam; - PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), $command, 'index'); - PCAP::Threaded::touch_success(File::Spec->catdir($tmp, 'progress'), 'index'); + PCAP::Threaded::external_process_handler(catdir($tmp, 'logs'), $command, 'index'); + PCAP::Threaded::touch_success(catdir($tmp, 'progress'), 'index'); } return 1; } sub split_files { my $options = shift; - my $vcf = File::Spec->catdir($options->{'tmp'}, 'vcf'); + my $vcf = catdir($options->{'tmp'}, 'vcf'); my $patt = catfile($vcf, 'split_*.vcf'); my @files = sort glob $patt; return \@files; @@ -292,7 +291,7 @@ sub split_files { sub blat { my ($index_in, $options) = @_; my $tmp = $options->{'tmp'}; - my $vcf = File::Spec->catdir($tmp, 'vcf'); + my $vcf = catdir($tmp, 'vcf'); # -i run_PD26988a/tmpPindel/vcf/raw.vcf return 1 if(exists $options->{'index'} && $index_in != $options->{'index'}); @@ -300,7 +299,7 @@ sub blat { my @split_files = @{$options->{'split_files'}}; my @indicies = limited_indicies($options, $index_in, scalar @split_files); for my $index(@indicies) { - next if PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), $index); + next if PCAP::Threaded::success_exists(catdir($tmp, 'progress'), $index); my $split_file = $split_files[$index-1]; my $blat_file = $split_file; @@ -312,8 +311,8 @@ sub blat { $split_file, $blat_file; - PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), $command, $index); - PCAP::Threaded::touch_success(File::Spec->catdir($tmp, 'progress'), $index); + PCAP::Threaded::external_process_handler(catdir($tmp, 'logs'), $command, $index); + PCAP::Threaded::touch_success(catdir($tmp, 'progress'), $index); } return 1; } @@ -327,18 +326,18 @@ sub pindel_to_vcf { my @seqs = sort keys %{$options->{'seqs'}}; my @indicies = limited_indicies($options, $index_in, scalar @seqs); for my $index(@indicies) { - next if PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), $index); + next if PCAP::Threaded::success_exists(catdir($tmp, 'progress'), $index); my $seq = $seqs[$index-1]; - my $pout = File::Spec->catdir($tmp, 'pout'); + my $pout = catdir($tmp, 'pout'); my @in_files; for my $type(qw(D SI)) { - my $in_file = File::Spec->catfile($pout, $seq.'_'.$seq.'_'.$type); + my $in_file = catfile($pout, $seq.'_'.$seq.'_'.$type); push @in_files, $in_file if(-e $in_file && -f $in_file); } if(scalar @in_files > 0) { - my $vcf = File::Spec->catdir($tmp, 'vcf'); + my $vcf = catdir($tmp, 'vcf'); make_path($vcf) unless(-e $vcf); my $pg = Sanger::CGP::Pindel::OutputGen::BamUtil::pg_from_caller('pindel', 'cgpPindel indel detection', $VERSION, $options->{'cmd'}); @@ -348,8 +347,8 @@ sub pindel_to_vcf { $command .= sprintf $PIN_2_VCF, $options->{'tumour'}, $options->{'normal'}, $options->{'reference'}, - File::Spec->catfile($vcf, $seq.'_pindel.vcf'), - File::Spec->catfile($vcf, $seq.'_pindel'), + catfile($vcf, $seq.'_pindel.vcf'), + catfile($vcf, $seq.'_pindel'), $options->{'seqtype'}, $options->{'seqtype'}, $pg, @@ -359,13 +358,13 @@ sub pindel_to_vcf { $command .= ' -s' if(defined $options->{'skipgerm'}); $command .= ' -as '.$options->{'assembly'} if(defined $options->{'assembly'}); $command .= ' -sp '.$options->{'species'} if(defined $options->{'species'}); - PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), $command, $index); + PCAP::Threaded::external_process_handler(catdir($tmp, 'logs'), $command, $index); } # ## The rest is auto-magical - PCAP::Threaded::touch_success(File::Spec->catdir($tmp, 'progress'), $index); + PCAP::Threaded::touch_success(catdir($tmp, 'progress'), $index); } return 1; } @@ -374,10 +373,10 @@ sub merge_and_bam { my $options = shift; my $tmp = $options->{'tmp'}; - return 1 if PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), 0); + return 1 if PCAP::Threaded::success_exists(catdir($tmp, 'progress'), 0); - my $vcf = File::Spec->catdir($tmp, 'vcf'); - my $outstub = File::Spec->catfile($options->{'outdir'}, $options->{'tumour_name'}.'_vs_'.$options->{'normal_name'}); + my $vcf = catdir($tmp, 'vcf'); + my $outstub = catfile($options->{'outdir'}, $options->{'tumour_name'}.'_vs_'.$options->{'normal_name'}); my $command = "$^X "; $command .= _which('pindel_merge_vcf_bam.pl'); $command .= sprintf $PIN_MERGE, $outstub, $vcf, $options->{'reference'}; @@ -388,16 +387,16 @@ sub merge_and_bam { $command .= ' -s'; } - PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), $command, 0); + PCAP::Threaded::external_process_handler(catdir($tmp, 'logs'), $command, 0); - PCAP::Threaded::touch_success(File::Spec->catdir($tmp, 'progress'), 0); + PCAP::Threaded::touch_success(catdir($tmp, 'progress'), 0); } sub flag { my $options = shift; my $tmp = $options->{'tmp'}; - return 1 if PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), 0); + return 1 if PCAP::Threaded::success_exists(catdir($tmp, 'progress'), 0); # FlagVcf.pl # -r ~kr2/GitHub/cgpPindel/perl/rules/genomicRules.lst @@ -408,7 +407,7 @@ sub flag { # -i pindel_farm/PD13371a_vs_PD13371b.vcf.gz # -o pindel_farm/PD13371a_vs_PD13371b.flag_new_np.github.vcf - my $stub = File::Spec->catfile($options->{'outdir'}, $options->{'tumour_name'}.'_vs_'.$options->{'normal_name'}); + my $stub = catfile($options->{'outdir'}, $options->{'tumour_name'}.'_vs_'.$options->{'normal_name'}); my $new_vcf = "$stub.flagged.vcf"; my $command = "$^X "; @@ -437,11 +436,11 @@ sub flag { $germ .= sprintf $PIN_GERM, find_germline_rule($options), $vcf_gz, $germ_bed; push @commands, $germ; - PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), \@commands, 0); + PCAP::Threaded::external_process_handler(catdir($tmp, 'logs'), \@commands, 0); unlink $new_vcf; - PCAP::Threaded::touch_success(File::Spec->catdir($tmp, 'progress'), 0); + PCAP::Threaded::touch_success(catdir($tmp, 'progress'), 0); } sub find_germline_rule { @@ -515,12 +514,12 @@ sub determine_jobs { die "ERROR: Unexpected combination of BAM/CRAM inputs"; } for my $in_bam(@samples) { - my $samp_path = File::Spec->catdir($tmp, sanitised_sample_from_bam($in_bam)); + my $samp_path = catdir($tmp, sanitised_sample_from_bam($in_bam)); my @files = file_list($samp_path, qr/\.txt(:?\.gz)$/); for my $file(@files) { my ($seq) = $file =~ m/(.+)\.txt(:?\.gz)$/; if(first { $seq eq $_ } @valid_seqs) { - push @{$seqs{$seq}}, File::Spec->catfile($samp_path, $file); + push @{$seqs{$seq}}, catfile($samp_path, $file); } } } @@ -585,7 +584,7 @@ sub fragmented_files { sub _which { my $prog = shift; my $l_bin = $Bin; - my $path = File::Spec->catfile($l_bin, $prog); + my $path = catfile($l_bin, $prog); $path = which($prog) unless(-e $path); return $path; } @@ -649,7 +648,7 @@ sub shared_setup { PCAP::Cli::file_for_reading('reference', $opts{'reference'}); PCAP::Cli::out_dir_check('outdir', $opts{'outdir'}); - my $final_logs = File::Spec->catdir($opts{'outdir'}, 'logs'); + my $final_logs = catdir($opts{'outdir'}, 'logs'); if(-e $final_logs) { warn "NOTE: Presence of '$final_logs' directory suggests successful complete analysis, please delete to rerun\n"; exit 0; @@ -672,11 +671,11 @@ sub shared_setup { $opts{$key} = cwd().'/'.$opts{$key} if(defined $opts{$key} && -e $opts{$key} && $opts{$key} !~ m/^\//); } - my $tmpdir = File::Spec->catdir($opts{'outdir'}, 'tmpPindel'); + my $tmpdir = catdir($opts{'outdir'}, 'tmpPindel'); make_path($tmpdir) unless(-d $tmpdir); - my $progress = File::Spec->catdir($tmpdir, 'progress'); + my $progress = catdir($tmpdir, 'progress'); make_path($progress) unless(-d $progress); - my $logs = File::Spec->catdir($tmpdir, 'logs'); + my $logs = catdir($tmpdir, 'logs'); make_path($logs) unless(-d $logs); $opts{'tmp'} = $tmpdir; @@ -724,14 +723,14 @@ sub cohort_split { my $options = shift; my $tmp = $options->{'tmp'}; - return 1 if PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), 0); + return 1 if PCAP::Threaded::success_exists(catdir($tmp, 'progress'), 0); my $command = $^X.' '._which('pindelCohortVafSplit.pl'); $command .= sprintf ' -i %s -o %s -s %d', $options->{input}, $options->{split_dir}, $options->{size}; - PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), $command, 0); + PCAP::Threaded::external_process_handler(catdir($tmp, 'logs'), $command, 0); - PCAP::Threaded::touch_success(File::Spec->catdir($tmp, 'progress'), 0); + PCAP::Threaded::touch_success(catdir($tmp, 'progress'), 0); } sub fill_split_vaf { @@ -744,15 +743,15 @@ sub fill_split_vaf { my @indicies = limited_indicies($options, $index_in, scalar @split_files); for my $index(@indicies) { - next if PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), $index); + next if PCAP::Threaded::success_exists(catdir($tmp, 'progress'), $index); my $split_file = $split_files[$index-1]; - my $fill_basename = fileparse($split_file); - my $fill_file = catfile($options->{fill_dir}, $fill_basename); + my $fill_basename = fileparse($split_file, '.vcf.gz'); + my $fill_dir = catdir($options->{fill_dir}, $fill_basename); my $command = $^X.' '._which('pindelCohortVafSliceFill.pl'); - $command .= sprintf ' -r %s -i %s -o %s -d %s', $options->{ref}, $split_file, $fill_file, $options->{bwa_file_list}; + $command .= sprintf ' -r %s -i %s -o %s -d %s', $options->{ref}, $split_file, $fill_dir, $options->{bwa_file_list}; - PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), $command, $index); - PCAP::Threaded::touch_success(File::Spec->catdir($tmp, 'progress'), $index); + PCAP::Threaded::external_process_handler(catdir($tmp, 'logs'), $command, $index); + PCAP::Threaded::touch_success(catdir($tmp, 'progress'), $index); } } @@ -763,7 +762,7 @@ sub merge_vaf_bams { return 1 if(exists $options->{'index'} && $index_in != $options->{'index'}); my @indicies = limited_indicies($options, $index_in, scalar @{$options->{secondary_hts}}); for my $index(@indicies) { - next if PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), $index); + next if PCAP::Threaded::success_exists(catdir($tmp, 'progress'), $index); my $arr_idx = $index-1; my $sample = sanitised_sample_from_bam($options->{primary_hts}->[$arr_idx]); my $sort_tmp = catdir($options->{'tmp'}, sprintf 'sort_tmp_%d', $index); @@ -771,8 +770,8 @@ sub merge_vaf_bams { my $in_file_list = catfile($sort_tmp, 'merge_files'); my @split_bams; - for my $f(glob(catfile($options->{'fill_dir'}, sprintf '*.vcf.gz.%s.bam', $sample))) { - push @split_bams, $f if($f =~ m{/\d+\.vcf.gz.$sample\.bam$}); + for my $f(glob(catfile($options->{'fill_dir'}, '*', sprintf '%s.bam', $sample))) { + push @split_bams, $f if($f =~ m{/\d+/$sample\.bam$}); } # list of bams to merge lines_to_file($in_file_list, [$options->{secondary_hts}->[$arr_idx], @split_bams]); @@ -784,9 +783,9 @@ sub merge_vaf_bams { catfile($sort_tmp, 'samsort'), catfile($options->{output}, sprintf('%s.vaf.bam', $sample)); # outfile - PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), $command, $index); + PCAP::Threaded::external_process_handler(catdir($tmp, 'logs'), $command, $index); remove_tree($sort_tmp); - PCAP::Threaded::touch_success(File::Spec->catdir($tmp, 'progress'), $index); + PCAP::Threaded::touch_success(catdir($tmp, 'progress'), $index); } } @@ -801,7 +800,7 @@ sub lines_to_file { sub fill_vcf_merge { my $options = shift; my $tmp = $options->{'tmp'}; - return if PCAP::Threaded::success_exists(File::Spec->catdir($tmp, 'progress'), 0); + return if PCAP::Threaded::success_exists(catdir($tmp, 'progress'), 0); my @split_filled_vcf; for my $f(glob(catfile($options->{fill_dir}, '*.vcf.gz'))) { push @split_filled_vcf, $f if($f =~ m{/\d+\.vcf.gz$}); @@ -813,8 +812,8 @@ sub fill_vcf_merge { $final_vcf; my $tabix = sprintf q{tabix -fp vcf %s}, $final_vcf; - PCAP::Threaded::external_process_handler(File::Spec->catdir($tmp, 'logs'), [$merge, $tabix], 0); - PCAP::Threaded::touch_success(File::Spec->catdir($tmp, 'progress'), 0); + PCAP::Threaded::external_process_handler(catdir($tmp, 'logs'), [$merge, $tabix], 0); + PCAP::Threaded::touch_success(catdir($tmp, 'progress'), 0); } 1; diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm index 44181c7..956129c 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm @@ -38,6 +38,7 @@ use Sanger::CGP::Vcf::VcfProcessLog; use Sanger::CGP::PindelPostProcessing::VcfSoftFlagger; use Sanger::CGP::Vcf::VcfUtil; use PCAP::Bam::Bas; +use File::Spec::Functions; use Data::Dumper; @@ -107,11 +108,10 @@ sub _fa_dict { sub _align_output { my ($self, $outpath) = @_; $self->_fa_dict(); - $outpath =~ s/\.vcf$//; for my $sample(@{$self->{vcf_sample_order}}) { - my $sam_file = sprintf '%s.%s.sam.gz', $outpath, $sample; + my $sam_file = catfile($outpath, (sprintf '%s.sam.gz', $sample)); $self->{samfile}->{$sample} = $sam_file; - $self->{bamfile}->{$sample} = sprintf '%s.%s.bam', $outpath, $sample; + $self->{bamfile}->{$sample} = catfile($outpath, (sprintf '%s.bam', $sample)); unlink $sam_file if(-e $sam_file); my $SAM = new IO::Compress::Gzip $sam_file, -Level => Z_BEST_SPEED or die "IO::Compress::Gzip failed: $GzipError\n"; diff --git a/perl/t/data/blat/D.vcf b/perl/t/data/blat/D/test.vcf similarity index 100% rename from perl/t/data/blat/D.vcf rename to perl/t/data/blat/D/test.vcf diff --git a/perl/t/data/blat/DI.vcf b/perl/t/data/blat/DI/test.vcf similarity index 100% rename from perl/t/data/blat/DI.vcf rename to perl/t/data/blat/DI/test.vcf diff --git a/perl/t/data/blat/SI.vcf b/perl/t/data/blat/SI/test.vcf similarity index 100% rename from perl/t/data/blat/SI.vcf rename to perl/t/data/blat/SI/test.vcf diff --git a/perl/t/vcfBlatAugment.t b/perl/t/vcfBlatAugment.t index 1449c9c..e2a59d6 100644 --- a/perl/t/vcfBlatAugment.t +++ b/perl/t/vcfBlatAugment.t @@ -21,6 +21,7 @@ use strict; use File::Temp qw(tempdir); +use File::Path qw(make_path); use Test::More; use Test::Fatal; use File::Spec::Functions; @@ -47,11 +48,11 @@ my ($sam_stdout_fh, $sam_buffer); subtest 'Initialisation checks' => sub { use_ok($MODULE); - new_vba(catfile($DATA, 'D.vcf')); + new_vba(catdir($DATA, 'D')); }; subtest 'Header checks' => sub { - my $vba = new_vba(catfile($DATA, 'D.vcf')); + my $vba = new_vba(catdir($DATA, 'D')); ok($vba->output_header); my @lines = split /\n/, $buffer; is(scalar @lines, $HEADER_LINES, 'Expected number of header lines'); @@ -59,21 +60,21 @@ subtest 'Header checks' => sub { }; subtest 'Simple Deletion checks' => sub { - my $vba = new_vba(catfile($DATA, 'D.vcf')); + my $vba = new_vba(catdir($DATA, 'D')); my @tmp = @{$DATA_ARR_D}; $vba->blat_record(\@tmp); is_deeply(\@tmp, $RES_ARR_D); }; subtest 'Simple Insertion checks' => sub { - my $vba = new_vba(catfile($DATA, 'SI.vcf')); + my $vba = new_vba(catdir($DATA, 'SI')); my @tmp = @{$DATA_ARR_SI}; $vba->blat_record(\@tmp); is_deeply(\@tmp, $RES_ARR_SI); }; subtest 'Complex event checks' => sub { - my $vba = new_vba(catfile($DATA, 'DI.vcf')); + my $vba = new_vba(catdir($DATA, 'DI')); my @tmp = @{$DATA_ARR_DI}; $vba->blat_record(\@tmp); print join q{ }, @tmp; @@ -84,10 +85,11 @@ done_testing(); sub new_vba { - my $vcf = shift; + my $dir = shift; my $tmp = '/tmp/pindel_test_stuff'; + make_path($tmp); my $obj = new_ok($MODULE, [ - input => $vcf, + input => catfile($dir, 'test.vcf'), ref => catfile($DATA, 'chr10_1-23700.fa'), ofh => buffer_fh(), outpath => $tmp, From e61a3a9716a1eeec3408d5678b22f0f96787dc0e Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Fri, 4 Sep 2020 14:34:42 +0000 Subject: [PATCH 23/60] Fix up cohort tools that share VcfBlatAugment object for changes to constructor and files --- perl/bin/pindelCohort.pl | 2 - perl/bin/pindelCohortVafFill.pl | 6 +++ perl/bin/pindelCohortVafSliceFill.pl | 9 ++-- perl/bin/pindel_blat_vaf.pl | 16 ++++--- perl/lib/Sanger/CGP/Pindel/Implement.pm | 42 +++++++++---------- .../CGP/Pindel/OutputGen/VcfBlatAugment.pm | 16 +++---- 6 files changed, 48 insertions(+), 43 deletions(-) diff --git a/perl/bin/pindelCohort.pl b/perl/bin/pindelCohort.pl index a58ec17..0427a6b 100644 --- a/perl/bin/pindelCohort.pl +++ b/perl/bin/pindelCohort.pl @@ -37,8 +37,6 @@ BEGIN use PCAP::Cli; use Sanger::CGP::Pindel::Implement; -use Data::Dumper; - const my %INDEX_MAX => ( 'input' => 1, 'pindel' => -1, diff --git a/perl/bin/pindelCohortVafFill.pl b/perl/bin/pindelCohortVafFill.pl index 9eb3a43..21cde8c 100644 --- a/perl/bin/pindelCohortVafFill.pl +++ b/perl/bin/pindelCohortVafFill.pl @@ -145,6 +145,11 @@ sub setup { push @{$opts{secondary_hts}}, $secondary; } close $D; + my $sample_count = @{$opts{primary_hts}}; + if($opts{size} < $sample_count * 5) { + $opts{size} = $sample_count * 5; + warn sprintf "WARNING: -size has been automatically increased to %d (5x sample number), see '-help'\n", $opts{size}; + } $opts{tmp} = catdir($opts{output}, 'tmpCohortVafFill'); make_path($opts{tmp}); @@ -183,6 +188,7 @@ =head1 SYNOPSIS Optional parameters: -name -n Stub name for final output files [$output/cohort...] -size -s Number of Sample/event combinations per-file when processing [10000] + - Automatically increased to a minimum of 5x number of samples (for efficiency). -abort -a Abort noisily if data appears to have been processed (silent exit otherwise) -cpus -c Number of cores to use. [1] diff --git a/perl/bin/pindelCohortVafSliceFill.pl b/perl/bin/pindelCohortVafSliceFill.pl index ccadc35..3bc20ab 100644 --- a/perl/bin/pindelCohortVafSliceFill.pl +++ b/perl/bin/pindelCohortVafSliceFill.pl @@ -4,13 +4,13 @@ use warnings FATAL => 'all'; use autodie qw(:all); use Cwd qw(abs_path); -use Pod::Usage qw(pod2usage); +use File::Path qw(make_path); +use File::Spec::Functions; use FindBin qw($Bin); -use lib "$Bin/../lib"; use Getopt::Long; -use File::Spec::Functions; -use File::Path qw(make_path); use IO::Compress::Gzip qw(:constants gzip $GzipError); +use lib "$Bin/../lib"; +use Pod::Usage qw(pod2usage); use PCAP::Cli; use Sanger::CGP::Pindel::OutputGen::VcfBlatAugment; @@ -74,7 +74,6 @@ sub setup { $opts{output} = $ofh; return \%opts; - } __END__ diff --git a/perl/bin/pindel_blat_vaf.pl b/perl/bin/pindel_blat_vaf.pl index cde7fa8..e988755 100755 --- a/perl/bin/pindel_blat_vaf.pl +++ b/perl/bin/pindel_blat_vaf.pl @@ -4,10 +4,12 @@ use warnings FATAL => 'all'; use autodie qw(:all); use Cwd qw(abs_path); -use Pod::Usage qw(pod2usage); +use File::Path qw(make_path); +use File::Spec::Functions; use FindBin qw($Bin); -use lib "$Bin/../lib"; use Getopt::Long; +use lib "$Bin/../lib"; +use Pod::Usage qw(pod2usage); use PCAP::Cli; use Sanger::CGP::Pindel::OutputGen::VcfBlatAugment; @@ -67,10 +69,12 @@ sub setup{ } } - $opts{align} = $opts{output}.'.sam' unless(defined $opts{align}); - $opts{outpath} = $opts{output}; - open my $ofh, '>', $opts{output}; + make_path($opts{outpath}) unless(-e $opts{outpath}); + + $opts{align} = catfile($opts{outpath}, 'data.sam'); + + open my $ofh, '>', catfile($opts{outpath}, 'data.vcf'); $opts{output} = $ofh; return \%opts; @@ -90,7 +94,7 @@ =head1 SYNOPSIS -ref -r File path to the reference file used to provide the coordinate system. -input -i VCF file to read in. -hts BAM/CRAM file for associated sample. - -output -o File path for VCF output (not compressed) + -output -o Directory for VCF output (gz compressed) and colocated sample bams Other: -help -h Brief help message. diff --git a/perl/lib/Sanger/CGP/Pindel/Implement.pm b/perl/lib/Sanger/CGP/Pindel/Implement.pm index 417a624..dcf4759 100644 --- a/perl/lib/Sanger/CGP/Pindel/Implement.pm +++ b/perl/lib/Sanger/CGP/Pindel/Implement.pm @@ -25,25 +25,23 @@ package Sanger::CGP::Pindel::Implement; use strict; use warnings FATAL => 'all'; use autodie qw(:all); -use Cwd qw(cwd); +use Capture::Tiny; use Const::Fast qw(const); -use File::Spec::Functions; -use File::Which qw(which); +use Cwd qw(cwd); +use File::Basename; use File::Copy qw(copy); use File::Path qw(make_path remove_tree); +use File::Spec::Functions; use File::Temp qw(tempfile); -use Capture::Tiny; -use List::Util qw(first); +use File::Which qw(which); use FindBin qw($Bin); use Getopt::Long; +use List::Util qw(first); use Pod::Usage qw(pod2usage); -use File::Basename; -use Sanger::CGP::Pindel; - -use PCAP::Threaded; use PCAP::Bam; - +use PCAP::Threaded; +use Sanger::CGP::Pindel; use Sanger::CGP::Pindel::OutputGen::BamUtil; const my $PINDEL_GEN_COMM => q{ -b %s -o %s -t %s}; @@ -240,16 +238,16 @@ sub concat { my ($options) = @_; my $tmp = $options->{'tmp'}; - my $vcf = catdir($tmp, 'vcf'); my $hts_input = $options->{'hts_files'}->[0]; my $sample_name = (PCAP::Bam::sample_name($hts_input))[0]; my $vcf_gz = catfile($options->{'outdir'}, sprintf('%s.pindel.vcf.gz', $sample_name)); my $bam = catfile($options->{'outdir'}, sprintf('%s.pindel.bam', $sample_name)); unless(PCAP::Threaded::success_exists(catdir($tmp, 'progress'), 'concat')) { - #vcf-concat blat_*.vcf + #vcf-concat blat_*/data.vcf my $command = _which('vcf-concat'); $command .= sprintf q{ %s | bgzip -c > %s}, - catfile($vcf, 'blat_*.vcf'), + catfile($tmp, 'blat_*/data.vcf'), + #catfile($vcf, 'blat_*.vcf'), $vcf_gz; PCAP::Threaded::external_process_handler(catdir($tmp, 'logs'), ['set -o pipefail', $command], 'concat'); PCAP::Threaded::touch_success(catdir($tmp, 'progress'), 'concat'); @@ -265,8 +263,8 @@ sub concat { my $samtools = _which('samtools'); my $command = sprintf q{(%s view -H %s | grep -P '^@(HD|SQ)' && zgrep -hvP '^@(HD|SQ)' %s | sort | uniq) | %s sort -l 0 -T %s - | %s calmd -b - %s > %s}, $samtools, $hts_input, - catfile($vcf, sprintf 'blat_*.%s.sam.gz', $sample_name), - $samtools, catfile($vcf, 'srt'), + catfile($tmp, (sprintf 'blat_*/%s.sam.gz', $sample_name)), + $samtools, catfile($tmp, 'srt'), $samtools, $options->{'reference'}, $bam; PCAP::Threaded::external_process_handler(catdir($tmp, 'logs'), ['set -o pipefail', $command], 'calmd'); PCAP::Threaded::touch_success(catdir($tmp, 'progress'), 'calmd'); @@ -294,22 +292,22 @@ sub blat { my $vcf = catdir($tmp, 'vcf'); # -i run_PD26988a/tmpPindel/vcf/raw.vcf - return 1 if(exists $options->{'index'} && $index_in != $options->{'index'}); + return 1 if(exists $options->{index} && $index_in != $options->{index}); - my @split_files = @{$options->{'split_files'}}; + my @split_files = @{$options->{split_files}}; my @indicies = limited_indicies($options, $index_in, scalar @split_files); for my $index(@indicies) { next if PCAP::Threaded::success_exists(catdir($tmp, 'progress'), $index); my $split_file = $split_files[$index-1]; - my $blat_file = $split_file; + my $blat_file = fileparse($split_file, '.vcf'); $blat_file =~ s/split_([a-z]+)/blat_$1/; my $command = $^X.' '._which('pindel_blat_vaf.pl'); $command .= sprintf q{ -r %s -hts %s -i %s -o %s}, - $options->{'reference'}, + $options->{reference}, $options->{hts_files}->[0], $split_file, - $blat_file; + catfile($tmp, $blat_file); PCAP::Threaded::external_process_handler(catdir($tmp, 'logs'), $command, $index); PCAP::Threaded::touch_success(catdir($tmp, 'progress'), $index); @@ -802,8 +800,8 @@ sub fill_vcf_merge { my $tmp = $options->{'tmp'}; return if PCAP::Threaded::success_exists(catdir($tmp, 'progress'), 0); my @split_filled_vcf; - for my $f(glob(catfile($options->{fill_dir}, '*.vcf.gz'))) { - push @split_filled_vcf, $f if($f =~ m{/\d+\.vcf.gz$}); + for my $f(glob(catfile($options->{fill_dir}, '*', 'slice.vcf.gz'))) { + push @split_filled_vcf, $f if($f =~ m{/\d+/slice\.vcf\.gz$}); } my $complete_recs = catfile($options->{split_dir}, 'complete_rec.vaf.vcf.gz'); my $final_vcf = catfile($options->{output}, sprintf '%s.vaf.vcf.gz', $options->{name}); diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm index 956129c..c828b37 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm @@ -24,23 +24,23 @@ package Sanger::CGP::Pindel::OutputGen::VcfBlatAugment; use strict; use warnings FATAL => 'all'; use autodie qw(:all); +use Capture::Tiny qw(capture); use Const::Fast qw(const); use File::Basename; -use List::Util qw(min max); +use File::Spec::Functions; use File::Temp qw(tempfile); -use Capture::Tiny qw(capture); use IO::Compress::Gzip qw(:constants gzip $GzipError); -use Vcf; +use List::Util qw(min max); + use Bio::DB::HTS; use Bio::DB::HTS::Faidx; +use Vcf; + +use PCAP::Bam::Bas; use Sanger::CGP::Pindel; -use Sanger::CGP::Vcf::VcfProcessLog; use Sanger::CGP::PindelPostProcessing::VcfSoftFlagger; +use Sanger::CGP::Vcf::VcfProcessLog; use Sanger::CGP::Vcf::VcfUtil; -use PCAP::Bam::Bas; -use File::Spec::Functions; - -use Data::Dumper; const my $SD_MULT => 2; const my $V_FMT => 8; From a15da93d869b5e1dd6b5329cda9126ba816379b1 Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Fri, 25 Sep 2020 15:43:37 +0000 Subject: [PATCH 24/60] typo --- perl/bin/pindelCohortMerge.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/perl/bin/pindelCohortMerge.pl b/perl/bin/pindelCohortMerge.pl index 4a96a11..dad974c 100644 --- a/perl/bin/pindelCohortMerge.pl +++ b/perl/bin/pindelCohortMerge.pl @@ -234,7 +234,7 @@ =head1 SYNOPSIS Optional parameters: -min -k Keep events VAF >= VALUE (3dp) for 1 or more samples - default is to retain events even if VAF == 0/. for all samples - -np -n Normal panel gff3 file - ommit if no filtering required. + -np -n Normal panel gff3 file - omit if no filtering required. -mnps -s Minimum normal panel samples required to exclude [default: >=1] -control -c Exclude any events where this sample has calls. From 538b07752c187062d51bec5075cf2761c4933a56 Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Fri, 25 Sep 2020 15:44:05 +0000 Subject: [PATCH 25/60] Cleanup so restarts work if failure occured during samtools sort --- perl/lib/Sanger/CGP/Pindel/Implement.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/perl/lib/Sanger/CGP/Pindel/Implement.pm b/perl/lib/Sanger/CGP/Pindel/Implement.pm index dcf4759..7db5d30 100644 --- a/perl/lib/Sanger/CGP/Pindel/Implement.pm +++ b/perl/lib/Sanger/CGP/Pindel/Implement.pm @@ -261,7 +261,8 @@ sub concat { # now deal with the sam files unless(PCAP::Threaded::success_exists(catdir($tmp, 'progress'), 'calmd')) { my $samtools = _which('samtools'); - my $command = sprintf q{(%s view -H %s | grep -P '^@(HD|SQ)' && zgrep -hvP '^@(HD|SQ)' %s | sort | uniq) | %s sort -l 0 -T %s - | %s calmd -b - %s > %s}, + my $command = sprintf q{rm -f %s && (%s view -H %s | grep -P '^@(HD|SQ)' && zgrep -hvP '^@(HD|SQ)' %s | sort | uniq) | %s sort -l 0 -T %s - | %s calmd -b - %s > %s}, + catfile($tmp, 'srt.????.bam'), # cleanup anything that may be have been left by previous run $samtools, $hts_input, catfile($tmp, (sprintf 'blat_*/%s.sam.gz', $sample_name)), $samtools, catfile($tmp, 'srt'), From 01e4f382cc9a1f2f292f8d3921a2ec0bba786824 Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Tue, 30 Mar 2021 11:18:32 +0100 Subject: [PATCH 26/60] some lagging differences --- perl/bin/pindelCohortMerge.pl | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/perl/bin/pindelCohortMerge.pl b/perl/bin/pindelCohortMerge.pl index dad974c..60b4130 100644 --- a/perl/bin/pindelCohortMerge.pl +++ b/perl/bin/pindelCohortMerge.pl @@ -162,6 +162,15 @@ sub np_lookup { return \%tree; } +sub vcf_list { + my $list_file = shift; + my @vcfs; + open my $LIST, '<', $list_file; + map { chomp $_; push @vcfs, $_; } <$LIST>; + close $LIST; + return \@vcfs; +} + sub setup{ my %opts = ( 'cmd' => join(" ", $0, @ARGV), @@ -177,6 +186,7 @@ sub setup{ 'k|min:f' => \$opts{min_vaf}, 'd|debug' => \$opts{debug}, 'c|control=s' => \$opts{control}, + 'l|list:s' => \$opts{list}, ); if(defined $opts{'v'}) { @@ -195,7 +205,13 @@ sub setup{ $opts{min_vaf} = (sprintf '%.3f', $opts{min_vaf}) + 0; # force number to be stored } - my @vcfs = @ARGV; + my @vcfs; + if($opts{list}) { + @vcfs = @{vcf_list($opts{list})}; + } + else { + @vcfs = @ARGV; + } $opts{vcfs} = \@vcfs; if(@vcfs < 2) { @@ -226,12 +242,15 @@ =head1 NAME =head1 SYNOPSIS -pindelCohortMerge.pl [options] A.vcf.gz B.vcf.gz [...] +pindelCohortMerge.pl [options] A.vcf.gz B.vcf.gz... +or +pindelCohortMerge.pl [options] -l vcf.list Required parameters: -output -o File path for VCF output (not compressed) Optional parameters: + -list -l Text list of vcf files, 1 per line -min -k Keep events VAF >= VALUE (3dp) for 1 or more samples - default is to retain events even if VAF == 0/. for all samples -np -n Normal panel gff3 file - omit if no filtering required. From ea0b2c405b4c819efd4679673a4039128a0496f2 Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Thu, 20 May 2021 09:59:11 +0100 Subject: [PATCH 27/60] docker cleanup --- Dockerfile | 52 ++++++++++++++++++++++------------------------------ 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/Dockerfile b/Dockerfile index 03799a1..80fc163 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM quay.io/wtsicgp/pcap-core:5.2.2 as builder +FROM quay.io/wtsicgp/pcap-core:5.6.1 as builder USER root @@ -8,29 +8,23 @@ ENV VER_CGPVCF="v2.2.1"\ VER_VCFTOOLS="0.1.16"\ VER_BLAT="v385" -RUN apt-get -yq update -RUN apt-get install -yq --no-install-recommends locales -RUN apt-get install -yq --no-install-recommends g++ -RUN apt-get install -yq --no-install-recommends make -RUN apt-get install -yq --no-install-recommends gcc -RUN apt-get install -yq --no-install-recommends pkg-config -RUN apt-get install -yq --no-install-recommends zlib1g-dev - -RUN locale-gen en_US.UTF-8 -RUN update-locale LANG=en_US.UTF-8 +RUN apt-get -yq update \ +&& apt-get install -yq --no-install-recommends locales g++ make gcc pkg-config zlib1g-dev \ +&& locale-gen en_US.UTF-8 \ +&& update-locale LANG=en_US.UTF-8 ENV OPT /opt/wtsi-cgp -ENV PATH $OPT/bin:$OPT/biobambam2/bin:$PATH -ENV PERL5LIB $OPT/lib/perl5 -ENV LD_LIBRARY_PATH $OPT/lib -ENV LC_ALL en_US.UTF-8 -ENV LANG en_US.UTF-8 +ENV PATH=$OPT/bin:$OPT/biobambam2/bin:$PATH \ + PERL5LIB=$OPT/lib/perl5 \ + LD_LIBRARY_PATH=$OPT/lib \ + LC_ALL=en_US.UTF-8 \ + LANG=en_US.UTF-8 # build tools from other repos -ADD build/opt-build.sh build/ +COPY build/opt-build.sh build/ RUN bash build/opt-build.sh $OPT -ADD build/opt-build-local.sh build/ +COPY build/opt-build-local.sh build/ COPY c++ c++ COPY perl perl @@ -44,8 +38,8 @@ LABEL maintainer="cgphelp@sanger.ac.uk" \ version="v3.4.0" \ description="cgpPindel docker" -RUN apt-get -yq update -RUN apt-get install -yq --no-install-recommends \ +RUN apt-get -yq update \ +&& apt-get install -yq --no-install-recommends \ apt-transport-https \ locales \ curl \ @@ -65,19 +59,17 @@ google-perftools \ unattended-upgrades && \ unattended-upgrade -d -v && \ apt-get remove -yq unattended-upgrades && \ -apt-get autoremove -yq - -RUN locale-gen en_US.UTF-8 -RUN update-locale LANG=en_US.UTF-8 +apt-get autoremove -yq \ +&& locale-gen en_US.UTF-8 \ +&& update-locale LANG=en_US.UTF-8 ENV OPT /opt/wtsi-cgp -ENV PATH $OPT/bin:$OPT/biobambam2/bin:$PATH -ENV PERL5LIB $OPT/lib/perl5 -ENV LD_LIBRARY_PATH $OPT/lib -ENV LC_ALL en_US.UTF-8 -ENV LANG en_US.UTF-8 +ENV PATH=$OPT/bin:$OPT/biobambam2/bin:$PATH \ + PERL5LIB=$OPT/lib/perl5 \ + LD_LIBRARY_PATH=$OPT/lib \ + LC_ALL=en_US.UTF-8 \ + LANG=en_US.UTF-8 -RUN mkdir -p $OPT COPY --from=builder $OPT $OPT ## USER CONFIGURATION From 08135e5a3bb72f1321ea7d2d2d2cee896607df4e Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Thu, 20 May 2021 09:59:26 +0100 Subject: [PATCH 28/60] add in new filter form Stan --- perl/bin/pindelCohortMerge.pl | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/perl/bin/pindelCohortMerge.pl b/perl/bin/pindelCohortMerge.pl index 60b4130..aa46c15 100644 --- a/perl/bin/pindelCohortMerge.pl +++ b/perl/bin/pindelCohortMerge.pl @@ -26,10 +26,10 @@ # write stuff header($options->{output}, $options->{vcfs}, $sample_head); -records($options->{output}, $records, $sample_order, $options->{min_vaf}, $options->{np}, $options->{control}); +records($options->{output}, $records, $sample_order, $options->{min_vaf}, $options->{np}, $options->{control}, $options->{min_blat}); sub records { - my ($output, $records, $sample_order, $min_vaf, $np_tree, $control) = @_; + my ($output, $records, $sample_order, $min_vaf, $np_tree, $control, $min_blat) = @_; my %ds = %{$records}; my $uuid_gen = Data::UUID->new; my @samples = @{$sample_order}; @@ -57,18 +57,25 @@ sub records { my ($ref, $alt) = split ':', $seq_key; my $row = join "\t", $chr, $pos, $uuid_gen->to_string($uuid_gen->create), $ref, $alt, q{.}, q{}, $info, $format; my $samples_with_min_vaf = 0; + my $samples_with_min_blat = 0; for my $s(@samples) { if(exists $ds{$chr}{$pos}{$seq_key}{$s}) { + #GT:S1:S2:PP:NP:WTP:WTN:WTM:MTP:MTN:MTM:VAF + #./.:12:205.534:3:2:4:3:.:3:2:0.003:0.417 + my ($mtp, $mtn, $vaf) = (split /:/, $ds{$chr}{$pos}{$seq_key}{$s})[8,9,11]; $row .= "\t".$ds{$chr}{$pos}{$seq_key}{$s}; - my ($last_vaf) = $row =~ m/:([0-9.]+)$/; - $last_vaf = 0 if($last_vaf eq q{.}); - $samples_with_min_vaf++ if($last_vaf >= $min_vaf); + # need to review if these are all q{.} when one is + $vaf = 0 if($vaf eq q{.}); + $mtp = 0 if($mtp eq q{.}); + $mtn = 0 if($mtn eq q{.}); + $samples_with_min_vaf++ if($vaf >= $min_vaf); + $samples_with_min_blat++ if($mtp >= $min_blat && $mtn >= $min_blat); } else { $row .= "\t."; } } - if($samples_with_min_vaf > 0) { + if($samples_with_min_vaf > 0 && $samples_with_min_blat > 0) { print $output $row."\n"; } } @@ -176,6 +183,7 @@ sub setup{ 'cmd' => join(" ", $0, @ARGV), 'mnps' => 1, 'min_vaf' => 0, + 'min_blat' => 4, ); GetOptions( 'h|help' => \$opts{h}, 'm|man' => \$opts{m}, @@ -184,6 +192,7 @@ sub setup{ 'n|np=s' => \$opts{np}, 's|mnps=i' => \$opts{mnps}, 'k|min:f' => \$opts{min_vaf}, + 'b|blat:i' => \$opts{min_blat}, 'd|debug' => \$opts{debug}, 'c|control=s' => \$opts{control}, 'l|list:s' => \$opts{list}, @@ -254,8 +263,9 @@ =head1 SYNOPSIS -min -k Keep events VAF >= VALUE (3dp) for 1 or more samples - default is to retain events even if VAF == 0/. for all samples -np -n Normal panel gff3 file - omit if no filtering required. - -mnps -s Minimum normal panel samples required to exclude [default: >=1] - -control -c Exclude any events where this sample has calls. + -mnps -s Minimum normal panel samples required to exclude [default: 1] + -control -c Exclude events where this sample has calls. + -blat -b Exclude events where no sample has MTP >= N && MTN >= N [default: 4] Other: -help -h Brief help message. From 95e1b485d8dca0023f7fc3747225b063c05f0c0e Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Thu, 20 May 2021 10:00:03 +0100 Subject: [PATCH 29/60] hide vs-code workspace files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6a8bcf5..67462a3 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ .idea/* /python/env /tmp +*.code-workspace From a73c82bbe134415c9c9565a55a360f9d49441dc3 Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Tue, 24 Aug 2021 11:04:16 +0100 Subject: [PATCH 30/60] correct cli --- perl/bin/pindelCohortVafFill.pl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/perl/bin/pindelCohortVafFill.pl b/perl/bin/pindelCohortVafFill.pl index 21cde8c..86c0b44 100644 --- a/perl/bin/pindelCohortVafFill.pl +++ b/perl/bin/pindelCohortVafFill.pl @@ -106,7 +106,7 @@ sub setup { delete $opts{'index'} unless(defined $opts{'index'}); delete $opts{'limit'} unless(defined $opts{'limit'}); - for my $param(qw(input ref output)) { + for my $param(qw(input ref output data)) { pod2usage(-verbose => 1, -message=> sprintf('ERROR: -%s must be defined', $param), -exit => 2) unless(defined $opts{$param}); } @@ -176,10 +176,10 @@ =head1 SYNOPSIS pindelCohortVafFill.pl [options] -i ... -o ... -r ... -d ... Required parameters: - -file -f VCF file to read in. + -input -f VCF file to read in. -output -o Workspace directory and final output. -ref -r File path to the reference file used to provide the coordinate system. - -data -d File containing list of sequence data files for all samples used in "-file" + -data -d File containing list of sequence data files for all samples used in "-input" - format: tab separated BWA mapping followed by pindel_cohort reads, one sample per line. sample_A_bwa.bamsample_A_pindel.bam From 68a877a9151e2fc859b0075bea0fbe6b75c6e393 Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Thu, 26 Aug 2021 09:52:58 +0100 Subject: [PATCH 31/60] Cleanup legacy dev practices, add new method of handling license headers --- .circleci/config.yml | 16 ++ .dockerignore | 2 + .licenserc.yaml | 93 ++++++++++++ .pre-commit-config.yaml | 27 ++++ LICENCE => LICENSE | 4 +- README.md | 139 +++++++++++------- perl/Makefile.PL | 35 +++-- perl/bin/FlagVcf.pl | 34 +++-- perl/bin/pindel.pl | 35 +++-- perl/bin/pindelCohort.pl | 36 +++-- perl/bin/pindelCohortMerge.pl | 29 ++++ perl/bin/pindelCohortVafFill.pl | 29 ++++ perl/bin/pindelCohortVafSliceFill.pl | 29 ++++ perl/bin/pindelCohortVafSplit.pl | 29 ++++ perl/bin/pindelCohort_to_vcf.pl | 35 +++-- perl/bin/pindel_2_combined_vcf.pl | 35 +++-- perl/bin/pindel_blat_vaf.pl | 29 ++++ perl/bin/pindel_germ_bed.pl | 35 +++-- perl/bin/pindel_input_gen.pl | 35 +++-- perl/bin/pindel_merge_vcf_bam.pl | 35 +++-- perl/bin/pindel_np_from_vcf.pl | 35 +++-- perl/bin/pindel_np_remsample.pl | 29 ++++ perl/bin/pindel_vcfSortNsplit.pl | 29 ++++ perl/bin/prep_np_release.pl | 29 ++++ perl/lib/Sanger/CGP/Pindel.pm | 37 +++-- perl/lib/Sanger/CGP/Pindel/Implement.pm | 37 +++-- perl/lib/Sanger/CGP/Pindel/InputGen.pm | 37 +++-- perl/lib/Sanger/CGP/Pindel/InputGen/Pair.pm | 37 +++-- .../CGP/Pindel/InputGen/PairToPindel.pm | 38 +++-- perl/lib/Sanger/CGP/Pindel/InputGen/Read.pm | 37 +++-- .../Sanger/CGP/Pindel/InputGen/SamHeader.pm | 37 +++-- .../Sanger/CGP/Pindel/OutputGen/BamUtil.pm | 37 +++-- .../CGP/Pindel/OutputGen/CombinedRecord.pm | 37 +++-- .../OutputGen/CombinedRecordGenerator.pm | 37 +++-- .../CGP/Pindel/OutputGen/PindelRecord.pm | 37 +++-- .../Pindel/OutputGen/PindelRecordParser.pm | 37 +++-- .../CGP/Pindel/OutputGen/VcfBlatAugment.pm | 40 ++--- .../Pindel/OutputGen/VcfCohortConverter.pm | 39 +++-- .../CGP/Pindel/OutputGen/VcfConverter.pm | 37 +++-- .../CGP/PindelPostProcessing/AbstractExe.pm | 37 +++-- .../CGP/PindelPostProcessing/FilterRules.pm | 37 +++-- .../FragmentFilterRules.pm | 37 +++-- .../PindelPostProcessing/VcfSoftFlagger.pm | 37 +++-- perl/t/1_pm_compile.t | 21 +-- perl/t/2_pl_compile.t | 21 +-- perl/t/inputGen.t | 22 +-- perl/t/inputGenRead.t | 22 +-- perl/t/outputGenCombinedRecordGenerator.t | 21 +-- perl/t/outputGenPindelRecordParser.t | 21 +-- perl/t/outputGenVcfConverter.t | 21 +-- perl/t/vcfBlatAugment.t | 22 --- perl/t/vcfPindelFlagger.t | 6 - perl/util/pairedSplit.pl | 29 ++++ prerelease.sh | 79 ---------- python/pindelMmPlots.py | 29 ++++ setup.sh | 35 +++-- 56 files changed, 1171 insertions(+), 750 deletions(-) create mode 100644 .licenserc.yaml create mode 100644 .pre-commit-config.yaml rename LICENCE => LICENSE (99%) mode change 100644 => 100755 perl/bin/pindelCohort.pl mode change 100644 => 100755 perl/bin/pindelCohortMerge.pl mode change 100644 => 100755 perl/bin/pindelCohortVafFill.pl mode change 100644 => 100755 perl/bin/pindelCohortVafSliceFill.pl mode change 100644 => 100755 perl/bin/pindelCohort_to_vcf.pl mode change 100644 => 100755 perl/util/pairedSplit.pl delete mode 100755 prerelease.sh diff --git a/.circleci/config.yml b/.circleci/config.yml index 6de20c2..b4aaa09 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,6 +10,16 @@ version: 2.1 jobs: + license_chk: + machine: + # need machine as want to mount a volume + image: ubuntu-2004:202107-02 + steps: + - checkout + - run: + name: Execute skywalking-eyes check of licenses + command: | + docker run --rm -v $(pwd):/github/workspace apache/skywalking-eyes header check build: environment: IMAGE_NAME: quay.io/wtsicgp/cgppindel @@ -55,7 +65,13 @@ workflows: version: 2.1 build_test: jobs: + - license_chk: + filters: + tags: + only: /.+/ - build: + requires: + - license_chk context: - dockerhub-casmservice - quayio-casmservice diff --git a/.dockerignore b/.dockerignore index b72f8d2..76bab9e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -12,3 +12,5 @@ /perl/docs.tar.gz /python/env /install_tmp +/.circleci +/*.code-workspace diff --git a/.licenserc.yaml b/.licenserc.yaml new file mode 100644 index 0000000..4566875 --- /dev/null +++ b/.licenserc.yaml @@ -0,0 +1,93 @@ +header: + license: + spdx-id: AGPL-3.0-or-later + copyright-owner: Genome Research Ltd + content: | + Copyright (c) 2014-2021 + + Author: CASM/Cancer IT + + This file is part of cgpPindel. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + 1. The usage of a range of years within a copyright statement contained within + this distribution should be interpreted as being equivalent to a list of years + including the first and last year specified and all consecutive years between + them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- + 2009, 2011-2012’ should be interpreted as being identical to a statement that + reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright + statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being + identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, + 2009, 2010, 2011, 2012’. + + pattern: | + Copyright \(c\) \d+ + + Author: .+ + + This file is part of .+ + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or \(at your option\) any later version. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + 1. The usage of a range of years within a copyright statement contained within + this distribution should be interpreted as being equivalent to a list of years + including the first and last year specified and all consecutive years between + them. For example, a copyright statement that reads ‘Copyright \(c\) 2005, 2007- + 2009, 2011-2012’ should be interpreted as being identical to a statement that + reads ‘Copyright \(c\) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright + statement that reads ‘Copyright \(c\) 2005-2012’ should be interpreted as being + identical to a statement that reads ‘Copyright \(c\) 2005, 2006, 2007, 2008, + 2009, 2010, 2011, 2012’. + + paths: + - '**' + + paths-ignore: + - '.circleci' + - '.coveragerc' + - '.dockerignore' + - '.gitignore' + - '.pre-commit-config.yaml' + - 'CHANGES.md' + - 'Dockerfile' + - 'LICENSE' + - 'pyproject.toml' + - 'README.md' + - '**/*.yaml' + - 'tests/data/**/*' + - '**/*.egg-info/PKG-INFO' + - '.pytest_cache/' + - 'build/' + - 'MANIFEST.in' + - 'tests/htmlcov/' + - '.eggs/' + - 'c++/*.cpp' # not ours, distributed via agreement + - 'perl/t/' + - 'perl/rules/' + - 'perl/pm_to_blib' + - 'perl/blib/' + - 'perl/MANIFEST*' + - 'perl/MYMETA*' + - '.github/' + - 'INSTALL' + - 'Makefile' + + comment: on-failure diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..3f4f01d --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,27 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.0.1 + hooks: + - id: check-added-large-files + args: ['--maxkb=3000'] + - id: check-ast + - id: check-executables-have-shebangs + - id: check-shebang-scripts-are-executable + - id: check-merge-conflict + - id: check-toml + - id: check-yaml + - id: detect-aws-credentials + args: [--allow-missing-credentials] + - id: detect-private-key + - id: end-of-file-fixer + - id: name-tests-test + - id: requirements-txt-fixer + - id: trailing-whitespace +- repo: https://github.com/executablebooks/mdformat + rev: 0.7.6 + hooks: + - id: mdformat +- repo: https://github.com/hadolint/hadolint + rev: v2.4.1 + hooks: + - id: hadolint-docker diff --git a/LICENCE b/LICENSE similarity index 99% rename from LICENCE rename to LICENSE index dba13ed..636867c 100644 --- a/LICENCE +++ b/LICENSE @@ -1,7 +1,7 @@ GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 - Copyright (C) 2007 Free Software Foundation, Inc. + Copyright (c) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. @@ -630,7 +630,7 @@ state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. - Copyright (C) + Copyright (c) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by diff --git a/README.md b/README.md index 129d63c..710628a 100644 --- a/README.md +++ b/README.md @@ -6,37 +6,37 @@ cgpPindel contains the Cancer Genome Projects workflow for [Pindel][pindel-core] The is a lightly modified version of pindel v2.0 with CGP specific processing for: -* Input file generation -* Conversion from pindel text output to: - * tumour and normal BAM alignment files - * VCF - * Application of VCF filters. +- Input file generation +- Conversion from pindel text output to: + - tumour and normal BAM alignment files + - VCF + - Application of VCF filters. Contents: -* [Docker, Singularity and Dockstore](#docker-singularity-and-dockstore) -* [Dependencies/Install](#dependenciesinstall) -* [Creating a release](#creating-a-release) - * [Preparation](#preparation) - * [Release process](#release-process) - * [Code changes](#code-changes) - * [Testing](#testing) - * [Regression CI](#regression-ci) - * [Public CI](#public-ci) - * [Cutting the release](#cutting-the-release) -* [LICENCE](#licence) +- [Docker, Singularity and Dockstore](#docker-singularity-and-dockstore) +- [Dependencies/Install](#dependenciesinstall) +- [Creating a release](#creating-a-release) + - [Preparation](#preparation) + - [Release process](#release-process) + - [Code changes](#code-changes) + - [Testing](#testing) + - [Regression CI](#regression-ci) + - [Public CI](#public-ci) + - [Cutting the release](#cutting-the-release) +- [LICENCE](#licence) ## Docker, Singularity and Dockstore There are pre-built images containing this codebase on quay.io. When pulling an image you must specify the version there is no `latest`. -* [cgpPindel quay.io][quay-repo]: Contained within this repository - * Smallest build required to use cgpPindel - * Not linked to Dockstore (yet) - * Updated most frequently -* [dockstore-cgpwxs][ds-cgpwxs-git]: Contains tools specific to WXS analysis. -* [dockstore-cgpwgs][ds-cgpwgs-git]: Contains additional tools for WGS analysis. +- [cgpPindel quay.io][quay-repo]: Contained within this repository + - Smallest build required to use cgpPindel + - Not linked to Dockstore (yet) + - Updated most frequently +- [dockstore-cgpwxs][ds-cgpwxs-git]: Contains tools specific to WXS analysis. +- [dockstore-cgpwgs][ds-cgpwgs-git]: Contains additional tools for WGS analysis. These were primarily designed for use with dockstore.org but can be used as normal containers. @@ -46,8 +46,8 @@ The docker images are known to work correctly after import into a singularity im When doing a native install please install the following first: -* [PCAP-core v2.0+][pcap-core-rel] -* [cgpVcf v2.0+][cgpvcf-rel] +- [PCAP-core v2.0+][pcap-core-rel] +- [cgpVcf v2.0+][cgpvcf-rel] Please see these for any child dependencies. @@ -63,52 +63,79 @@ are installed into the target area. Please be aware that this expects basic C compilation libraries and tools to be available. -## Creating a release +## Developers -### Preparation +Please use `pre-commit` on this project. You can install to `$HOME/bin` via: -* Commit/push all relevant changes. -* Pull a clean version of the repo and use this for the following steps. +```bash +curl https://pre-commit.com/install-local.py | python - +``` -### Release process +In you checkout please run: -This project is maintained using the [HubFlow][hubflow-docs] methodology. +```bash +pre-commit install +``` + +### Updating licence headers + +Please use [skywalking-eyes](https://github.com/apache/skywalking-eyes). + +Expected workflow: + +1. Check state before modifying `.licenserc.yaml`: + - `docker run -it --rm -v $(pwd):/github/workspace apache/skywalking-eyes header check` + - You should get some 'valid' here, those without a header as 'invalid' +1. Modify `.licenserc.yaml` +1. Apply the changes: + - `docker run -it --rm -v $(pwd):/github/workspace apache/skywalking-eyes header fix` +1. Add/commit changes + +This is executed in the CI pipeline. -#### Code changes +*DO NOT* edit the header in the files, please modify the date component of `content` in `.licenserc.yaml`. The only exception being: + +- `README.md` + +If you need to make more extensive changes to the license carefully test the pattern is functional. + +### Code changes + +This project is maintained using the [HubFlow][hubflow-docs] methodology. 1. Make appropriate changes -2. Update `perl/lib/Sanger/CGP/Pindel.pm` to the correct version (adding rc/beta to end if applicable). -3. Update `CHANGES.md` to show major items. -4. Commit the updated docs and updated module/version. -5. Push commits. +1. Update `perl/lib/Sanger/CGP/Pindel.pm` to the correct version (adding rc/beta to end if applicable). +1. Update `CHANGES.md` to show major items. +1. Commit the updated docs and updated module/version. +1. Push commits. -#### Testing +### Testing -##### Regression CI +#### Regression CI An internal CI system is used to validate each release using real, large scale datasets. -##### Public CI +#### Public CI Circleci is used to: -* Build Docker image (unit tests are part of build) -* Validate expected tools exist -* For tags only: push image to quay.io +- Build Docker image (unit tests are part of build) +- Validate expected tools exist +- For tags only: push image to quay.io CI only runs for: -* Branches with pull-requests -* Default branch (`dev`) -* Tags +- Branches with pull-requests +- Default branch (`dev`) +- Tags #### Cutting the release Internal regression CI processes must be completed prior to this. 1. Check state on [Circleci][circle-repo] -2. Generate the release (add notes to GitHub) -3. Confirm that image has been built on [quay.io][quay-builds] +1. Generate the release (add notes to GitHub) +1. Confirm that image has been pushed to [quay.io][quay-tags] ## LICENCE @@ -144,19 +171,19 @@ identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, ``` -[cgpvcf-rel]: https://github.com/cancerit/cgpVcf/releases -[pcap-core-rel]: https://github.com/cancerit/PCAP-core/releases -[ds-cgpwxs-git]: https://github.com/cancerit/dockstore-cgpwxs -[ds-cgpwgs-git]: https://github.com/cancerit/dockstore-cgpwgs -[pindel-core]: http://gmt.genome.wustl.edu/pindel/current -[hubflow-docs]: https://datasift.github.io/gitflow/ -[circle-repo]: https://app.circleci.com/pipelines/github/cancerit/cgpPindel -[circle-badge-svg]: https://circleci.com/gh/cancerit/cgpPindel.svg?style=svg + -[circle-badge-link]: https://travis-ci.org/cancerit/cgpPindel.svg?branch=dev + +[cgpvcf-rel]: https://github.com/cancerit/cgpVcf/releases +[circle-repo]: https://app.circleci.com/pipelines/github/cancerit/cgpPindel +[ds-cgpwgs-git]: https://github.com/cancerit/dockstore-cgpwgs +[ds-cgpwxs-git]: https://github.com/cancerit/dockstore-cgpwxs +[hubflow-docs]: https://datasift.github.io/gitflow/ +[pcap-core-rel]: https://github.com/cancerit/PCAP-core/releases +[pindel-core]: http://gmt.genome.wustl.edu/pindel/current [quay-repo]: https://quay.io/repository/wtsicgp/cgppindel -[quay-builds]: https://quay.io/repository/wtsicgp/cgppindel?tab=builds +[quay-tags]: https://quay.io/repository/wtsicgp/cgppindel?tab=tags diff --git a/perl/Makefile.PL b/perl/Makefile.PL index 0f3d685..2563c37 100755 --- a/perl/Makefile.PL +++ b/perl/Makefile.PL @@ -1,26 +1,33 @@ #!/usr/bin/perl - -########## LICENCE ########## -# Copyright (c) 2014-2021 Genome Research Ltd. +# Copyright (c) 2014-2021 # # Author: CASM/Cancer IT # # This file is part of cgpPindel. # -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more -# details. +# 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## - +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# use ExtUtils::MakeMaker; diff --git a/perl/bin/FlagVcf.pl b/perl/bin/FlagVcf.pl index e07c5fb..4317f26 100755 --- a/perl/bin/FlagVcf.pl +++ b/perl/bin/FlagVcf.pl @@ -1,25 +1,33 @@ #!/usr/bin/perl - -########## LICENCE ########## -# Copyright (c) 2014-2021 Genome Research Ltd. +# Copyright (c) 2014-2021 # # Author: CASM/Cancer IT # # This file is part of cgpPindel. # -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more -# details. +# 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# my $config_path; BEGIN { diff --git a/perl/bin/pindel.pl b/perl/bin/pindel.pl index be6c092..f8ba0ec 100755 --- a/perl/bin/pindel.pl +++ b/perl/bin/pindel.pl @@ -1,26 +1,33 @@ #!/usr/bin/perl - -########## LICENCE ########## -# Copyright (c) 2014-2021 Genome Research Ltd. +# Copyright (c) 2014-2021 # # Author: CASM/Cancer IT # # This file is part of cgpPindel. # -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more -# details. +# 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## - +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# BEGIN { use Cwd qw(abs_path cwd); diff --git a/perl/bin/pindelCohort.pl b/perl/bin/pindelCohort.pl old mode 100644 new mode 100755 index 0427a6b..6cfbad7 --- a/perl/bin/pindelCohort.pl +++ b/perl/bin/pindelCohort.pl @@ -1,26 +1,33 @@ #!/usr/bin/perl - -########## LICENCE ########## -# Copyright (c) 2020 Genome Research Ltd. +# Copyright (c) 2014-2021 # # Author: CASM/Cancer IT # # This file is part of cgpPindel. # -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more -# details. +# 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## - +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# BEGIN { use Cwd qw(abs_path cwd); use File::Basename; @@ -212,4 +219,3 @@ =head1 OPTIONS If you want STDOUT/ERR to screen ensure index is set even for single job steps. =back - diff --git a/perl/bin/pindelCohortMerge.pl b/perl/bin/pindelCohortMerge.pl old mode 100644 new mode 100755 index aa46c15..c534cc8 --- a/perl/bin/pindelCohortMerge.pl +++ b/perl/bin/pindelCohortMerge.pl @@ -1,4 +1,33 @@ #!/usr/bin/env perl +# Copyright (c) 2014-2021 +# +# Author: CASM/Cancer IT +# +# This file is part of cgpPindel. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# use strict; use warnings FATAL => 'all'; diff --git a/perl/bin/pindelCohortVafFill.pl b/perl/bin/pindelCohortVafFill.pl old mode 100644 new mode 100755 index 86c0b44..61dfc36 --- a/perl/bin/pindelCohortVafFill.pl +++ b/perl/bin/pindelCohortVafFill.pl @@ -1,4 +1,33 @@ #!/usr/bin/env perl +# Copyright (c) 2014-2021 +# +# Author: CASM/Cancer IT +# +# This file is part of cgpPindel. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# use strict; use warnings FATAL => 'all'; diff --git a/perl/bin/pindelCohortVafSliceFill.pl b/perl/bin/pindelCohortVafSliceFill.pl old mode 100644 new mode 100755 index 3bc20ab..85b3188 --- a/perl/bin/pindelCohortVafSliceFill.pl +++ b/perl/bin/pindelCohortVafSliceFill.pl @@ -1,4 +1,33 @@ #!/usr/bin/env perl +# Copyright (c) 2014-2021 +# +# Author: CASM/Cancer IT +# +# This file is part of cgpPindel. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# use strict; use warnings FATAL => 'all'; diff --git a/perl/bin/pindelCohortVafSplit.pl b/perl/bin/pindelCohortVafSplit.pl index 2a6dac7..516ae47 100755 --- a/perl/bin/pindelCohortVafSplit.pl +++ b/perl/bin/pindelCohortVafSplit.pl @@ -1,4 +1,33 @@ #!/usr/bin/env perl +# Copyright (c) 2014-2021 +# +# Author: CASM/Cancer IT +# +# This file is part of cgpPindel. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# use strict; use warnings FATAL => 'all'; diff --git a/perl/bin/pindelCohort_to_vcf.pl b/perl/bin/pindelCohort_to_vcf.pl old mode 100644 new mode 100755 index bafe3f0..115ea9a --- a/perl/bin/pindelCohort_to_vcf.pl +++ b/perl/bin/pindelCohort_to_vcf.pl @@ -1,26 +1,33 @@ #!/usr/bin/perl - -########## LICENCE ########## -# Copyright (c) 2020 Genome Research Ltd. +# Copyright (c) 2014-2021 # # Author: CASM/Cancer IT # # This file is part of cgpPindel. # -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more -# details. +# 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## - +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# use strict; use warnings FATAL => 'all'; use autodie qw(:all); diff --git a/perl/bin/pindel_2_combined_vcf.pl b/perl/bin/pindel_2_combined_vcf.pl index fb64c6b..cf8cbd4 100755 --- a/perl/bin/pindel_2_combined_vcf.pl +++ b/perl/bin/pindel_2_combined_vcf.pl @@ -1,26 +1,33 @@ #!/usr/bin/perl - -########## LICENCE ########## -# Copyright (c) 2014-2021 Genome Research Ltd. +# Copyright (c) 2014-2021 # # Author: CASM/Cancer IT # # This file is part of cgpPindel. # -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more -# details. +# 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## - +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# use strict; use warnings FATAL => 'all'; diff --git a/perl/bin/pindel_blat_vaf.pl b/perl/bin/pindel_blat_vaf.pl index e988755..92057cd 100755 --- a/perl/bin/pindel_blat_vaf.pl +++ b/perl/bin/pindel_blat_vaf.pl @@ -1,4 +1,33 @@ #!/usr/bin/env perl +# Copyright (c) 2014-2021 +# +# Author: CASM/Cancer IT +# +# This file is part of cgpPindel. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# use strict; use warnings FATAL => 'all'; diff --git a/perl/bin/pindel_germ_bed.pl b/perl/bin/pindel_germ_bed.pl index 814f77b..4893b90 100755 --- a/perl/bin/pindel_germ_bed.pl +++ b/perl/bin/pindel_germ_bed.pl @@ -1,26 +1,33 @@ #!/usr/bin/perl - -########## LICENCE ########## -# Copyright (c) 2014-2021 Genome Research Ltd. +# Copyright (c) 2014-2021 # # Author: CASM/Cancer IT # # This file is part of cgpPindel. # -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more -# details. +# 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## - +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# BEGIN { use Cwd qw(abs_path); diff --git a/perl/bin/pindel_input_gen.pl b/perl/bin/pindel_input_gen.pl index 3fc82ca..eab5951 100755 --- a/perl/bin/pindel_input_gen.pl +++ b/perl/bin/pindel_input_gen.pl @@ -1,26 +1,33 @@ #!/usr/bin/perl - -########## LICENCE ########## -# Copyright (c) 2014-2021 Genome Research Ltd. +# Copyright (c) 2014-2021 # # Author: CASM/Cancer IT # # This file is part of cgpPindel. # -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more -# details. +# 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## - +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# BEGIN { use Cwd qw(abs_path); diff --git a/perl/bin/pindel_merge_vcf_bam.pl b/perl/bin/pindel_merge_vcf_bam.pl index 2d755b0..619a562 100755 --- a/perl/bin/pindel_merge_vcf_bam.pl +++ b/perl/bin/pindel_merge_vcf_bam.pl @@ -1,26 +1,33 @@ #!/usr/bin/perl - -########## LICENCE ########## -# Copyright (c) 2014-2021 Genome Research Ltd. +# Copyright (c) 2014-2021 # # Author: CASM/Cancer IT # # This file is part of cgpPindel. # -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more -# details. +# 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## - +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# BEGIN { use Cwd qw(abs_path); use File::Basename; diff --git a/perl/bin/pindel_np_from_vcf.pl b/perl/bin/pindel_np_from_vcf.pl index 832258a..d3cdd8f 100755 --- a/perl/bin/pindel_np_from_vcf.pl +++ b/perl/bin/pindel_np_from_vcf.pl @@ -1,26 +1,33 @@ #!/usr/bin/perl - -########## LICENCE ########## -# Copyright (c) 2014-2021 Genome Research Ltd. +# Copyright (c) 2014-2021 # # Author: CASM/Cancer IT # # This file is part of cgpPindel. # -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more -# details. +# 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## - +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# BEGIN { use Cwd qw(abs_path); diff --git a/perl/bin/pindel_np_remsample.pl b/perl/bin/pindel_np_remsample.pl index feb39b6..8b72078 100755 --- a/perl/bin/pindel_np_remsample.pl +++ b/perl/bin/pindel_np_remsample.pl @@ -1,4 +1,33 @@ #!/usr/bin/perl +# Copyright (c) 2014-2021 +# +# Author: CASM/Cancer IT +# +# This file is part of cgpPindel. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# use strict; use warnings FATAL=>'all'; diff --git a/perl/bin/pindel_vcfSortNsplit.pl b/perl/bin/pindel_vcfSortNsplit.pl index a465cf6..274df77 100755 --- a/perl/bin/pindel_vcfSortNsplit.pl +++ b/perl/bin/pindel_vcfSortNsplit.pl @@ -1,4 +1,33 @@ #!/usr/bin/env perl +# Copyright (c) 2014-2021 +# +# Author: CASM/Cancer IT +# +# This file is part of cgpPindel. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# use strict; use warnings FATAL => 'all'; diff --git a/perl/bin/prep_np_release.pl b/perl/bin/prep_np_release.pl index 8342c6c..af6b80f 100755 --- a/perl/bin/prep_np_release.pl +++ b/perl/bin/prep_np_release.pl @@ -1,4 +1,33 @@ #!/usr/bin/perl +# Copyright (c) 2014-2021 +# +# Author: CASM/Cancer IT +# +# This file is part of cgpPindel. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# use strict; use warnings FATAL=>'all'; diff --git a/perl/lib/Sanger/CGP/Pindel.pm b/perl/lib/Sanger/CGP/Pindel.pm index 2ccc297..9c7c0b5 100644 --- a/perl/lib/Sanger/CGP/Pindel.pm +++ b/perl/lib/Sanger/CGP/Pindel.pm @@ -1,26 +1,33 @@ -package Sanger::CGP::Pindel; - -########## LICENCE ########## -# Copyright (c) 2014-2021 Genome Research Ltd. +# Copyright (c) 2014-2021 # # Author: CASM/Cancer IT # # This file is part of cgpPindel. # -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more -# details. +# 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## - +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# +package Sanger::CGP::Pindel; use strict; use Const::Fast qw(const); diff --git a/perl/lib/Sanger/CGP/Pindel/Implement.pm b/perl/lib/Sanger/CGP/Pindel/Implement.pm index 552ff05..1c325c8 100644 --- a/perl/lib/Sanger/CGP/Pindel/Implement.pm +++ b/perl/lib/Sanger/CGP/Pindel/Implement.pm @@ -1,26 +1,33 @@ -package Sanger::CGP::Pindel::Implement; - -########## LICENCE ########## -# Copyright (c) 2014-2021 Genome Research Ltd. +# Copyright (c) 2014-2021 # # Author: CASM/Cancer IT # # This file is part of cgpPindel. # -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more -# details. +# 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## - +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# +package Sanger::CGP::Pindel::Implement; use strict; use warnings FATAL => 'all'; diff --git a/perl/lib/Sanger/CGP/Pindel/InputGen.pm b/perl/lib/Sanger/CGP/Pindel/InputGen.pm index ed5d640..4fd8499 100644 --- a/perl/lib/Sanger/CGP/Pindel/InputGen.pm +++ b/perl/lib/Sanger/CGP/Pindel/InputGen.pm @@ -1,26 +1,33 @@ -package Sanger::CGP::Pindel::InputGen; - -########## LICENCE ########## -# Copyright (c) 2014-2021 Genome Research Ltd. +# Copyright (c) 2014-2021 # # Author: CASM/Cancer IT # # This file is part of cgpPindel. # -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more -# details. +# 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## - +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# +package Sanger::CGP::Pindel::InputGen; use strict; use English qw( -no_match_vars ); diff --git a/perl/lib/Sanger/CGP/Pindel/InputGen/Pair.pm b/perl/lib/Sanger/CGP/Pindel/InputGen/Pair.pm index be70271..b01ee04 100644 --- a/perl/lib/Sanger/CGP/Pindel/InputGen/Pair.pm +++ b/perl/lib/Sanger/CGP/Pindel/InputGen/Pair.pm @@ -1,26 +1,33 @@ -package Sanger::CGP::Pindel::InputGen::Pair; - -########## LICENCE ########## -# Copyright (c) 2014-2021 Genome Research Ltd. +# Copyright (c) 2014-2021 # # Author: CASM/Cancer IT # # This file is part of cgpPindel. # -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more -# details. +# 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## - +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# +package Sanger::CGP::Pindel::InputGen::Pair; use strict; use English qw( -no_match_vars ); diff --git a/perl/lib/Sanger/CGP/Pindel/InputGen/PairToPindel.pm b/perl/lib/Sanger/CGP/Pindel/InputGen/PairToPindel.pm index 06699e4..cb850eb 100644 --- a/perl/lib/Sanger/CGP/Pindel/InputGen/PairToPindel.pm +++ b/perl/lib/Sanger/CGP/Pindel/InputGen/PairToPindel.pm @@ -1,26 +1,33 @@ -package Sanger::CGP::Pindel::InputGen::PairToPindel; - -########## LICENCE ########## -# Copyright (c) 2014-2021 Genome Research Ltd. +# Copyright (c) 2014-2021 # # Author: CASM/Cancer IT # # This file is part of cgpPindel. # -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more -# details. +# 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## - +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# +package Sanger::CGP::Pindel::InputGen::PairToPindel; use strict; use English qw( -no_match_vars ); @@ -135,4 +142,3 @@ sub _self_anchored_to_pindel { 1; __DATA__ - diff --git a/perl/lib/Sanger/CGP/Pindel/InputGen/Read.pm b/perl/lib/Sanger/CGP/Pindel/InputGen/Read.pm index 2d2b9cc..6721a8e 100644 --- a/perl/lib/Sanger/CGP/Pindel/InputGen/Read.pm +++ b/perl/lib/Sanger/CGP/Pindel/InputGen/Read.pm @@ -1,26 +1,33 @@ -package Sanger::CGP::Pindel::InputGen::Read; - -########## LICENCE ########## -# Copyright (c) 2014-2021 Genome Research Ltd. +# Copyright (c) 2014-2021 # # Author: CASM/Cancer IT # # This file is part of cgpPindel. # -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more -# details. +# 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## - +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# +package Sanger::CGP::Pindel::InputGen::Read; use strict; use English qw( -no_match_vars ); diff --git a/perl/lib/Sanger/CGP/Pindel/InputGen/SamHeader.pm b/perl/lib/Sanger/CGP/Pindel/InputGen/SamHeader.pm index 3048d8d..b28fde5 100644 --- a/perl/lib/Sanger/CGP/Pindel/InputGen/SamHeader.pm +++ b/perl/lib/Sanger/CGP/Pindel/InputGen/SamHeader.pm @@ -1,26 +1,33 @@ -package Sanger::CGP::Pindel::InputGen::SamHeader; - -########## LICENCE ########## -# Copyright (c) 2014-2021 Genome Research Ltd. +# Copyright (c) 2014-2021 # # Author: CASM/Cancer IT # # This file is part of cgpPindel. # -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more -# details. +# 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## - +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# +package Sanger::CGP::Pindel::InputGen::SamHeader; use strict; use English qw( -no_match_vars ); diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/BamUtil.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/BamUtil.pm index f20abdc..ee33c49 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/BamUtil.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/BamUtil.pm @@ -1,26 +1,33 @@ -package Sanger::CGP::Pindel::OutputGen::BamUtil; - -########## LICENCE ########## -# Copyright (c) 2014-2021 Genome Research Ltd. +# Copyright (c) 2014-2021 # # Author: CASM/Cancer IT # # This file is part of cgpPindel. # -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more -# details. +# 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## - +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# +package Sanger::CGP::Pindel::OutputGen::BamUtil; use Sanger::CGP::Pindel; use Sanger::CGP::Pindel::Implement; diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/CombinedRecord.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/CombinedRecord.pm index e140a67..c8b42d1 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/CombinedRecord.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/CombinedRecord.pm @@ -1,26 +1,33 @@ -package Sanger::CGP::Pindel::OutputGen::CombinedRecord; - -########## LICENCE ########## -# Copyright (c) 2014-2021 Genome Research Ltd. +# Copyright (c) 2014-2021 # # Author: CASM/Cancer IT # # This file is part of cgpPindel. # -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more -# details. +# 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## - +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# +package Sanger::CGP::Pindel::OutputGen::CombinedRecord; use Sanger::CGP::Pindel; diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/CombinedRecordGenerator.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/CombinedRecordGenerator.pm index 79dbb1f..f6ec5b5 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/CombinedRecordGenerator.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/CombinedRecordGenerator.pm @@ -1,26 +1,33 @@ -package Sanger::CGP::Pindel::OutputGen::CombinedRecordGenerator; - -########## LICENCE ########## -# Copyright (c) 2014-2021 Genome Research Ltd. +# Copyright (c) 2014-2021 # # Author: CASM/Cancer IT # # This file is part of cgpPindel. # -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more -# details. +# 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## - +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# +package Sanger::CGP::Pindel::OutputGen::CombinedRecordGenerator; use Sanger::CGP::Pindel; diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/PindelRecord.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/PindelRecord.pm index 3739489..f8692a6 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/PindelRecord.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/PindelRecord.pm @@ -1,26 +1,33 @@ -package Sanger::CGP::Pindel::OutputGen::PindelRecord; - -########## LICENCE ########## -# Copyright (c) 2014-2021 Genome Research Ltd. +# Copyright (c) 2014-2021 # # Author: CASM/Cancer IT # # This file is part of cgpPindel. # -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more -# details. +# 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## - +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# +package Sanger::CGP::Pindel::OutputGen::PindelRecord; use Sanger::CGP::Pindel; diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/PindelRecordParser.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/PindelRecordParser.pm index ad930ef..37e50e1 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/PindelRecordParser.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/PindelRecordParser.pm @@ -1,26 +1,33 @@ -package Sanger::CGP::Pindel::OutputGen::PindelRecordParser; - -########## LICENCE ########## -# Copyright (c) 2014-2021 Genome Research Ltd. +# Copyright (c) 2014-2021 # # Author: CASM/Cancer IT # # This file is part of cgpPindel. # -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more -# details. +# 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## - +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# +package Sanger::CGP::Pindel::OutputGen::PindelRecordParser; use Sanger::CGP::Pindel; diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm index c828b37..e589c99 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm @@ -1,26 +1,33 @@ -package Sanger::CGP::Pindel::OutputGen::VcfBlatAugment; - -########## LICENCE ########## -# Copyright (c) 2020 Genome Research Ltd. +# Copyright (c) 2014-2021 # -# Author: CASM IT +# Author: CASM/Cancer IT # # This file is part of cgpPindel. # -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more -# details. +# 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## - +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# +package Sanger::CGP::Pindel::OutputGen::VcfBlatAugment; use strict; use warnings FATAL => 'all'; use autodie qw(:all); @@ -604,4 +611,3 @@ sub blat_ref_alt { $v_h->{change_alt} = $change_alt; return 1; } - diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfCohortConverter.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfCohortConverter.pm index a6501ce..f989cb0 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfCohortConverter.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfCohortConverter.pm @@ -1,26 +1,33 @@ -package Sanger::CGP::Pindel::OutputGen::VcfCohortConverter; - -########## LICENCE ########## -# Copyright (c) 2020 Genome Research Ltd. +# Copyright (c) 2014-2021 # -# Author: CASM IT +# Author: CASM/Cancer IT # # This file is part of cgpPindel. # -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more -# details. +# 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## - +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# +package Sanger::CGP::Pindel::OutputGen::VcfCohortConverter; use strict; use File::Basename; use File::Temp qw(tempfile); diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfConverter.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfConverter.pm index cba97a4..e3d139d 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfConverter.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfConverter.pm @@ -1,26 +1,33 @@ -package Sanger::CGP::Pindel::OutputGen::VcfConverter; - -########## LICENCE ########## -# Copyright (c) 2014-2021 Genome Research Ltd. +# Copyright (c) 2014-2021 # # Author: CASM/Cancer IT # # This file is part of cgpPindel. # -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more -# details. +# 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## - +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# +package Sanger::CGP::Pindel::OutputGen::VcfConverter; use Sanger::CGP::Pindel; diff --git a/perl/lib/Sanger/CGP/PindelPostProcessing/AbstractExe.pm b/perl/lib/Sanger/CGP/PindelPostProcessing/AbstractExe.pm index 64044d0..afd42d3 100644 --- a/perl/lib/Sanger/CGP/PindelPostProcessing/AbstractExe.pm +++ b/perl/lib/Sanger/CGP/PindelPostProcessing/AbstractExe.pm @@ -1,26 +1,33 @@ -package Sanger::CGP::PindelPostProcessing::AbstractExe; - -########## LICENCE ########## -# Copyright (c) 2014-2021 Genome Research Ltd. +# Copyright (c) 2014-2021 # # Author: CASM/Cancer IT # # This file is part of cgpPindel. # -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more -# details. +# 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## - +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# +package Sanger::CGP::PindelPostProcessing::AbstractExe; use FindBin; use Sanger::CGP::Pindel; use Exporter 'import'; diff --git a/perl/lib/Sanger/CGP/PindelPostProcessing/FilterRules.pm b/perl/lib/Sanger/CGP/PindelPostProcessing/FilterRules.pm index 13dbed9..92b8e81 100644 --- a/perl/lib/Sanger/CGP/PindelPostProcessing/FilterRules.pm +++ b/perl/lib/Sanger/CGP/PindelPostProcessing/FilterRules.pm @@ -1,26 +1,33 @@ -package Sanger::CGP::PindelPostProcessing::FilterRules; - -########## LICENCE ########## -# Copyright (c) 2014-2021 Genome Research Ltd. +# Copyright (c) 2014-2021 # # Author: CASM/Cancer IT # # This file is part of cgpPindel. # -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more -# details. +# 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## - +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# +package Sanger::CGP::PindelPostProcessing::FilterRules; use strict; use Bio::DB::HTS::Tabix; use Sanger::CGP::Pindel; diff --git a/perl/lib/Sanger/CGP/PindelPostProcessing/FragmentFilterRules.pm b/perl/lib/Sanger/CGP/PindelPostProcessing/FragmentFilterRules.pm index 5b2fdec..1e9f299 100644 --- a/perl/lib/Sanger/CGP/PindelPostProcessing/FragmentFilterRules.pm +++ b/perl/lib/Sanger/CGP/PindelPostProcessing/FragmentFilterRules.pm @@ -1,26 +1,33 @@ -package Sanger::CGP::PindelPostProcessing::FragmentFilterRules; - -########## LICENCE ########## -# Copyright (c) 2014-2021 Genome Research Ltd. +# Copyright (c) 2014-2021 # # Author: CASM/Cancer IT # # This file is part of cgpPindel. # -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more -# details. +# 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## - +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# +package Sanger::CGP::PindelPostProcessing::FragmentFilterRules; use strict; use Bio::DB::HTS::Tabix; use Sanger::CGP::Pindel; diff --git a/perl/lib/Sanger/CGP/PindelPostProcessing/VcfSoftFlagger.pm b/perl/lib/Sanger/CGP/PindelPostProcessing/VcfSoftFlagger.pm index a5a111e..3d71f81 100644 --- a/perl/lib/Sanger/CGP/PindelPostProcessing/VcfSoftFlagger.pm +++ b/perl/lib/Sanger/CGP/PindelPostProcessing/VcfSoftFlagger.pm @@ -1,26 +1,33 @@ -package Sanger::CGP::PindelPostProcessing::VcfSoftFlagger; - -########## LICENCE ########## -# Copyright (c) 2014-2021 Genome Research Ltd. +# Copyright (c) 2014-2021 # # Author: CASM/Cancer IT # # This file is part of cgpPindel. # -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more -# details. +# 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## - +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# +package Sanger::CGP::PindelPostProcessing::VcfSoftFlagger; use strict; use Carp; use English qw( -no_match_vars ); diff --git a/perl/t/1_pm_compile.t b/perl/t/1_pm_compile.t index bd870d5..a351fc5 100644 --- a/perl/t/1_pm_compile.t +++ b/perl/t/1_pm_compile.t @@ -1,23 +1,4 @@ -########## LICENCE ########## -# Copyright (c) 2014-2021 Genome Research Ltd. -# -# Author: CASM/Cancer IT -# -# This file is part of cgpPindel. -# -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 Affero General Public License for more -# details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## + # this is a catch all to ensure all modules do compile # added as lots of 'use' functionality is dynamic in pipeline diff --git a/perl/t/2_pl_compile.t b/perl/t/2_pl_compile.t index e76269c..c2d8bd5 100644 --- a/perl/t/2_pl_compile.t +++ b/perl/t/2_pl_compile.t @@ -1,23 +1,4 @@ -########## LICENCE ########## -# Copyright (c) 2014-2021 Genome Research Ltd. -# -# Author: CASM/Cancer IT -# -# This file is part of cgpPindel. -# -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 Affero General Public License for more -# details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## + # this is a catch all to ensure all modules do compile # added as lots of 'use' functionality is dynamic in pipeline diff --git a/perl/t/inputGen.t b/perl/t/inputGen.t index 0c6123f..9980942 100644 --- a/perl/t/inputGen.t +++ b/perl/t/inputGen.t @@ -1,23 +1,4 @@ -########## LICENCE ########## -# Copyright (c) 2014-2021 Genome Research Ltd. -# -# Author: CASM/Cancer IT -# -# This file is part of cgpPindel. -# -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 Affero General Public License for more -# details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## + use strict; use File::Temp qw(tempdir); @@ -76,4 +57,3 @@ subtest 'reads_to_disk checks' => sub{ }; done_testing(); - diff --git a/perl/t/inputGenRead.t b/perl/t/inputGenRead.t index 357c7ac..c83f77a 100644 --- a/perl/t/inputGenRead.t +++ b/perl/t/inputGenRead.t @@ -1,23 +1,4 @@ -########## LICENCE ########## -# Copyright (c) 2014-2021 Genome Research Ltd. -# -# Author: CASM/Cancer IT -# -# This file is part of cgpPindel. -# -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 Affero General Public License for more -# details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## + use strict; use Test::More; @@ -45,4 +26,3 @@ $obj = new_ok($MODULE, [\$cleaned, 2]); is($obj->frac_pbq_poor, $EXP_FRAC_PBQ, 'Check poor qual PBQ fraction'); done_testing(); - diff --git a/perl/t/outputGenCombinedRecordGenerator.t b/perl/t/outputGenCombinedRecordGenerator.t index 068e347..d1189ff 100644 --- a/perl/t/outputGenCombinedRecordGenerator.t +++ b/perl/t/outputGenCombinedRecordGenerator.t @@ -1,23 +1,4 @@ -########## LICENCE ########## -# Copyright (c) 2014-2021 Genome Research Ltd. -# -# Author: CASM/Cancer IT -# -# This file is part of cgpPindel. -# -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 Affero General Public License for more -# details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## + use strict; use Test::More; diff --git a/perl/t/outputGenPindelRecordParser.t b/perl/t/outputGenPindelRecordParser.t index c08ee72..8a4f7a9 100644 --- a/perl/t/outputGenPindelRecordParser.t +++ b/perl/t/outputGenPindelRecordParser.t @@ -1,23 +1,4 @@ -########## LICENCE ########## -# Copyright (c) 2014-2021 Genome Research Ltd. -# -# Author: CASM/Cancer IT -# -# This file is part of cgpPindel. -# -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 Affero General Public License for more -# details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## + use strict; use Test::More; diff --git a/perl/t/outputGenVcfConverter.t b/perl/t/outputGenVcfConverter.t index 17d5499..b44036c 100644 --- a/perl/t/outputGenVcfConverter.t +++ b/perl/t/outputGenVcfConverter.t @@ -1,23 +1,4 @@ -########## LICENCE ########## -# Copyright (c) 2014-2021 Genome Research Ltd. -# -# Author: CASM/Cancer IT -# -# This file is part of cgpPindel. -# -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 Affero General Public License for more -# details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## + use strict; use Test::More; diff --git a/perl/t/vcfBlatAugment.t b/perl/t/vcfBlatAugment.t index e2a59d6..3cdf4c2 100644 --- a/perl/t/vcfBlatAugment.t +++ b/perl/t/vcfBlatAugment.t @@ -1,24 +1,3 @@ -########## LICENCE ########## -# Copyright (c) 2019 Genome Research Ltd. -# -# Author: CASM/Cancer IT -# -# This file is part of cgpPindel. -# -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 Affero General Public License for more -# details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## - use strict; use File::Temp qw(tempdir); use File::Path qw(make_path); @@ -156,4 +135,3 @@ subtest 'reads_to_disk checks' => sub{ }; done_testing(); - diff --git a/perl/t/vcfPindelFlagger.t b/perl/t/vcfPindelFlagger.t index b99f2bb..2387235 100644 --- a/perl/t/vcfPindelFlagger.t +++ b/perl/t/vcfPindelFlagger.t @@ -1,9 +1,3 @@ -#################################################### -# Copyright (c) 2014-2021 Genome Research Ltd. -# Author: CASM/Cancer IT, cgphelp@sanger.ac.uk -# See LICENCE for details -#################################################### - use strict; use warnings; use Cwd 'abs_path'; diff --git a/perl/util/pairedSplit.pl b/perl/util/pairedSplit.pl old mode 100644 new mode 100755 index be80285..5c7f754 --- a/perl/util/pairedSplit.pl +++ b/perl/util/pairedSplit.pl @@ -1,4 +1,33 @@ #!/usr/bin/env perl +# Copyright (c) 2014-2021 +# +# Author: CASM/Cancer IT +# +# This file is part of cgpPindel. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# use strict; use v5.16; diff --git a/prerelease.sh b/prerelease.sh deleted file mode 100755 index fb6967a..0000000 --- a/prerelease.sh +++ /dev/null @@ -1,79 +0,0 @@ -#!/bin/bash - -########## LICENCE ########## -# Copyright (c) 2014-2021 Genome Research Ltd. -# -# Author: CASM/Cancer IT -# -# This file is part of cgpPindel. -# -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 Affero General Public License for more -# details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## - -set -eu # exit on first error or undefined value in subtitution - -# get current directory -INIT_DIR=`pwd` - -rm -rf blib - -# get location of this file -MY_PATH="`dirname \"$0\"`" # relative -MY_PATH="`( cd \"$MY_PATH\" && pwd )`" # absolutized and normalized -if [ -z "$MY_PATH" ] ; then - # error; for some reason, the path is not accessible - # to the script (e.g. permissions re-evaled after suid) - echo Failed to determine location of script >2 - exit 1 # fail -fi -# change into the location of the script -cd $MY_PATH/perl - -echo '### Running perl tests ###' -rm -rf reports docs pm_to_blib blib -cover -delete -export HARNESS_PERL_SWITCHES=-MDevel::Cover=-db,reports,-select='^lib/*\.pm$',-ignore,'^t/' -rm -rf docs -mkdir -p docs/reports_text -prove -w -I lib t - -echo '### Generating test/pod coverage reports ###' -# removed 'condition' from coverage as '||' 'or' doesn't work properly -cover -coverage branch,subroutine,pod -report_c0 50 -report_c1 85 -report_c2 100 -report html_basic reports -silent |& grep -v '^Perltidy' | grep -v '^##' | grep -v '^1:' -# grep on last command to cleanup an oddity in perltidy -cover -coverage branch,subroutine,pod -report text reports -silent > docs/reports_text/coverage.txt -rm -rf reports/structure perl.reports/digests reports/cover.13 reports/runs -cp reports/coverage.html reports/index.html -mv reports docs/reports_html -unset HARNESS_PERL_SWITCHES - -echo '### Generating POD ###' -mkdir -p docs/pod_html -perl -MPod::Simple::HTMLBatch -e 'Pod::Simple::HTMLBatch::go' lib:bin docs/pod_html > /dev/null - -echo '### Archiving docs folder ###' -tar cz -C $MY_PATH/perl -f docs.tar.gz docs - -# generate manifest, and cleanup -echo '### Generating MANIFEST ###' -# delete incase any files are moved, the make target just adds stuff -rm -f MANIFEST -# cleanup things which could break the manifest -rm -rf install_tmp -perl Makefile.PL > /dev/null -make manifest >& /dev/null -rm -f Makefile MANIFEST.bak pm_to_blib - -# change back to original dir -cd $INIT_DIR diff --git a/python/pindelMmPlots.py b/python/pindelMmPlots.py index fbce4ae..293d62d 100755 --- a/python/pindelMmPlots.py +++ b/python/pindelMmPlots.py @@ -1,4 +1,33 @@ #!/usr/bin/env python3 +# +# Copyright (c) 2014-2021 +# +# Author: CASM/Cancer IT +# +# This file is part of cgpPindel. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. # core libs import os # for mkdir, path stuff diff --git a/setup.sh b/setup.sh index 9dc4777..a345a55 100755 --- a/setup.sh +++ b/setup.sh @@ -1,25 +1,33 @@ #!/bin/bash - -########## LICENCE ########## -# Copyright (c) 2014-2021 Genome Research Ltd. +# +# Copyright (c) 2014-2021 # # Author: CASM/Cancer IT # # This file is part of cgpPindel. # -# cgpPindel is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero 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 free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more -# details. +# 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -########## LICENCE ########## +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. # ALL tool versions used by opt-build.sh # need to keep in sync with Dockerfile @@ -35,7 +43,6 @@ get_file () { fi } - if [[ ($# -ne 1 && $# -ne 2) ]] ; then echo "Please provide an installation path and optionally perl lib paths to allow, e.g." echo " ./setup.sh /opt/myBundle" From 089d30b88f07b0810d707062171322e89f23549f Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Thu, 26 Aug 2021 13:58:57 +0100 Subject: [PATCH 32/60] Correct c/p error --- perl/lib/Sanger/CGP/Pindel/Implement.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/perl/lib/Sanger/CGP/Pindel/Implement.pm b/perl/lib/Sanger/CGP/Pindel/Implement.pm index 1c325c8..ce2440a 100644 --- a/perl/lib/Sanger/CGP/Pindel/Implement.pm +++ b/perl/lib/Sanger/CGP/Pindel/Implement.pm @@ -511,7 +511,7 @@ sub determine_jobs { my %seqs; my @samples; if(exists $options->{'tumour'} && exists $options->{'normal'}) { - push @samples, $options->{'tumour'} && exists $options->{'normal'}; + push @samples, $options->{'tumour'}, $options->{'normal'}; } elsif(exists $options->{'hts_files'}) { @samples = @{$options->{'hts_files'}}; From 086c7ce111834c7af9b0555e07c285b9c79d7ed6 Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Thu, 26 Aug 2021 13:59:29 +0100 Subject: [PATCH 33/60] Indicate users should access wiki --- README.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 710628a..d7254c8 100644 --- a/README.md +++ b/README.md @@ -12,17 +12,18 @@ The is a lightly modified version of pindel v2.0 with CGP specific processing fo - VCF - Application of VCF filters. +Details of execution and referencing can be found in the [wiki][cgppindel-wiki] + Contents: - [Docker, Singularity and Dockstore](#docker-singularity-and-dockstore) - [Dependencies/Install](#dependenciesinstall) -- [Creating a release](#creating-a-release) - - [Preparation](#preparation) - - [Release process](#release-process) - - [Code changes](#code-changes) - - [Testing](#testing) - - [Regression CI](#regression-ci) - - [Public CI](#public-ci) +- [Developers](#developers) + - [Updating licence headers](#updating-licence-headers) + - [Code changes](#code-changes) + - [Testing](#testing) + - [Regression CI](#regression-ci) + - [Public CI](#public-ci) - [Cutting the release](#cutting-the-release) - [LICENCE](#licence) @@ -178,6 +179,7 @@ identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +[cgppindel-wiki]: https://github.com/cancerit/cgpPindel/wiki [cgpvcf-rel]: https://github.com/cancerit/cgpVcf/releases [circle-repo]: https://app.circleci.com/pipelines/github/cancerit/cgpPindel [ds-cgpwgs-git]: https://github.com/cancerit/dockstore-cgpwgs From 7d1c812361f86d026cfa3566ed352eee4df53844 Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Fri, 27 Aug 2021 11:05:04 +0100 Subject: [PATCH 34/60] remove comments --- perl/t/vcfBlatAugment.t | 38 -------------------------------------- 1 file changed, 38 deletions(-) diff --git a/perl/t/vcfBlatAugment.t b/perl/t/vcfBlatAugment.t index 3cdf4c2..d9a3c23 100644 --- a/perl/t/vcfBlatAugment.t +++ b/perl/t/vcfBlatAugment.t @@ -97,41 +97,3 @@ sub sam_buffer_fh { open $sam_stdout_fh, ">", \$sam_buffer or die $!; return $sam_stdout_fh; } - - - -__END__ -subtest 'corrupt_pindel_input checks' => sub { - # File of correct size (no NUL) - is( Sanger::CGP::Pindel::InputGen::corrupt_pindel_input("$DATA/inputGen-goodfile.txt.gz", 22), - undef, - 'corrupt_pindel_input - well formed compressed file, expected size'); - # File of incorrect size (no NUL) - is( Sanger::CGP::Pindel::InputGen::corrupt_pindel_input("$DATA/inputGen-goodfile.txt.gz", 9), - "$DATA/inputGen-goodfile.txt.gz", - 'corrupt_pindel_input - well formed compressed file, UNexpected size'); - # File of incorrect size + NUL - is( Sanger::CGP::Pindel::InputGen::corrupt_pindel_input("$DATA/inputGen-NUL.txt.gz", 22), - "$DATA/inputGen-NUL.txt.gz", - 'corrupt_pindel_input - NUL character in compressed file, expected size'); - # File of correct size + NUL - is( Sanger::CGP::Pindel::InputGen::corrupt_pindel_input("$DATA/inputGen-NUL.txt.gz", 9), - "$DATA/inputGen-NUL.txt.gz", - 'corrupt_pindel_input - NUL character in compressed file, UNexpected size'); -}; - -subtest 'reads_to_disk checks' => sub{ - $obj = new_ok($MODULE, - [ "$DATA/test.bam", - undef, - "$DATA/genome_22.fa"]); - my $out_folder = tempdir( 'pindelTests_XXXX', CLEANUP => 1 ); - - $obj->set_outdir($out_folder); - ok($obj->reads_to_disk($RECORD_SET), 'create output'); - is($obj->{'rname_bytes'}->{'22'}, $RECORD_OUT_BYTES, 'Verify bytes written captured'); - - ok($obj->validate, 'validate returns true') -}; - -done_testing(); From 31aea3b8b0394bf6c64f60dc0be899370c35c3a5 Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Fri, 27 Aug 2021 11:28:30 +0100 Subject: [PATCH 35/60] Correction to copyright line --- .licenserc.yaml | 4 ++-- perl/Makefile.PL | 2 +- perl/bin/FlagVcf.pl | 2 +- perl/bin/pindel.pl | 2 +- perl/bin/pindelCohort.pl | 2 +- perl/bin/pindelCohortMerge.pl | 2 +- perl/bin/pindelCohortVafFill.pl | 2 +- perl/bin/pindelCohortVafSliceFill.pl | 2 +- perl/bin/pindelCohortVafSplit.pl | 2 +- perl/bin/pindelCohort_to_vcf.pl | 2 +- perl/bin/pindel_2_combined_vcf.pl | 2 +- perl/bin/pindel_blat_vaf.pl | 2 +- perl/bin/pindel_germ_bed.pl | 2 +- perl/bin/pindel_input_gen.pl | 2 +- perl/bin/pindel_merge_vcf_bam.pl | 2 +- perl/bin/pindel_np_from_vcf.pl | 2 +- perl/bin/pindel_np_remsample.pl | 2 +- perl/bin/pindel_vcfSortNsplit.pl | 2 +- perl/bin/prep_np_release.pl | 2 +- perl/lib/Sanger/CGP/Pindel.pm | 2 +- perl/lib/Sanger/CGP/Pindel/Implement.pm | 2 +- perl/lib/Sanger/CGP/Pindel/InputGen.pm | 2 +- perl/lib/Sanger/CGP/Pindel/InputGen/Pair.pm | 2 +- perl/lib/Sanger/CGP/Pindel/InputGen/PairToPindel.pm | 2 +- perl/lib/Sanger/CGP/Pindel/InputGen/Read.pm | 2 +- perl/lib/Sanger/CGP/Pindel/InputGen/SamHeader.pm | 2 +- perl/lib/Sanger/CGP/Pindel/OutputGen/BamUtil.pm | 2 +- perl/lib/Sanger/CGP/Pindel/OutputGen/CombinedRecord.pm | 2 +- .../Sanger/CGP/Pindel/OutputGen/CombinedRecordGenerator.pm | 2 +- perl/lib/Sanger/CGP/Pindel/OutputGen/PindelRecord.pm | 2 +- perl/lib/Sanger/CGP/Pindel/OutputGen/PindelRecordParser.pm | 2 +- perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm | 2 +- perl/lib/Sanger/CGP/Pindel/OutputGen/VcfCohortConverter.pm | 2 +- perl/lib/Sanger/CGP/Pindel/OutputGen/VcfConverter.pm | 2 +- perl/lib/Sanger/CGP/PindelPostProcessing/AbstractExe.pm | 2 +- perl/lib/Sanger/CGP/PindelPostProcessing/FilterRules.pm | 2 +- .../Sanger/CGP/PindelPostProcessing/FragmentFilterRules.pm | 2 +- perl/lib/Sanger/CGP/PindelPostProcessing/VcfSoftFlagger.pm | 2 +- perl/util/pairedSplit.pl | 2 +- python/pindelMmPlots.py | 2 +- setup.sh | 2 +- 41 files changed, 42 insertions(+), 42 deletions(-) diff --git a/.licenserc.yaml b/.licenserc.yaml index 4566875..2011449 100644 --- a/.licenserc.yaml +++ b/.licenserc.yaml @@ -3,7 +3,7 @@ header: spdx-id: AGPL-3.0-or-later copyright-owner: Genome Research Ltd content: | - Copyright (c) 2014-2021 + Copyright (c) 2014-2021 Genome Research Ltd Author: CASM/Cancer IT @@ -33,7 +33,7 @@ header: 2009, 2010, 2011, 2012’. pattern: | - Copyright \(c\) \d+ + Copyright \(c\) \d+ .+ Author: .+ diff --git a/perl/Makefile.PL b/perl/Makefile.PL index 2563c37..e49aa87 100755 --- a/perl/Makefile.PL +++ b/perl/Makefile.PL @@ -1,5 +1,5 @@ #!/usr/bin/perl -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/perl/bin/FlagVcf.pl b/perl/bin/FlagVcf.pl index 4317f26..f4d3b2f 100755 --- a/perl/bin/FlagVcf.pl +++ b/perl/bin/FlagVcf.pl @@ -1,5 +1,5 @@ #!/usr/bin/perl -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/perl/bin/pindel.pl b/perl/bin/pindel.pl index f8ba0ec..d0c491a 100755 --- a/perl/bin/pindel.pl +++ b/perl/bin/pindel.pl @@ -1,5 +1,5 @@ #!/usr/bin/perl -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/perl/bin/pindelCohort.pl b/perl/bin/pindelCohort.pl index 6cfbad7..2b3802d 100755 --- a/perl/bin/pindelCohort.pl +++ b/perl/bin/pindelCohort.pl @@ -1,5 +1,5 @@ #!/usr/bin/perl -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/perl/bin/pindelCohortMerge.pl b/perl/bin/pindelCohortMerge.pl index c534cc8..35c5cef 100755 --- a/perl/bin/pindelCohortMerge.pl +++ b/perl/bin/pindelCohortMerge.pl @@ -1,5 +1,5 @@ #!/usr/bin/env perl -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/perl/bin/pindelCohortVafFill.pl b/perl/bin/pindelCohortVafFill.pl index 61dfc36..877740c 100755 --- a/perl/bin/pindelCohortVafFill.pl +++ b/perl/bin/pindelCohortVafFill.pl @@ -1,5 +1,5 @@ #!/usr/bin/env perl -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/perl/bin/pindelCohortVafSliceFill.pl b/perl/bin/pindelCohortVafSliceFill.pl index 85b3188..0d5e7f0 100755 --- a/perl/bin/pindelCohortVafSliceFill.pl +++ b/perl/bin/pindelCohortVafSliceFill.pl @@ -1,5 +1,5 @@ #!/usr/bin/env perl -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/perl/bin/pindelCohortVafSplit.pl b/perl/bin/pindelCohortVafSplit.pl index 516ae47..05c974b 100755 --- a/perl/bin/pindelCohortVafSplit.pl +++ b/perl/bin/pindelCohortVafSplit.pl @@ -1,5 +1,5 @@ #!/usr/bin/env perl -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/perl/bin/pindelCohort_to_vcf.pl b/perl/bin/pindelCohort_to_vcf.pl index 115ea9a..d394a4a 100755 --- a/perl/bin/pindelCohort_to_vcf.pl +++ b/perl/bin/pindelCohort_to_vcf.pl @@ -1,5 +1,5 @@ #!/usr/bin/perl -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/perl/bin/pindel_2_combined_vcf.pl b/perl/bin/pindel_2_combined_vcf.pl index cf8cbd4..d19fab1 100755 --- a/perl/bin/pindel_2_combined_vcf.pl +++ b/perl/bin/pindel_2_combined_vcf.pl @@ -1,5 +1,5 @@ #!/usr/bin/perl -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/perl/bin/pindel_blat_vaf.pl b/perl/bin/pindel_blat_vaf.pl index 92057cd..2f178ee 100755 --- a/perl/bin/pindel_blat_vaf.pl +++ b/perl/bin/pindel_blat_vaf.pl @@ -1,5 +1,5 @@ #!/usr/bin/env perl -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/perl/bin/pindel_germ_bed.pl b/perl/bin/pindel_germ_bed.pl index 4893b90..ab7e507 100755 --- a/perl/bin/pindel_germ_bed.pl +++ b/perl/bin/pindel_germ_bed.pl @@ -1,5 +1,5 @@ #!/usr/bin/perl -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/perl/bin/pindel_input_gen.pl b/perl/bin/pindel_input_gen.pl index eab5951..19b5466 100755 --- a/perl/bin/pindel_input_gen.pl +++ b/perl/bin/pindel_input_gen.pl @@ -1,5 +1,5 @@ #!/usr/bin/perl -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/perl/bin/pindel_merge_vcf_bam.pl b/perl/bin/pindel_merge_vcf_bam.pl index 619a562..34c2e11 100755 --- a/perl/bin/pindel_merge_vcf_bam.pl +++ b/perl/bin/pindel_merge_vcf_bam.pl @@ -1,5 +1,5 @@ #!/usr/bin/perl -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/perl/bin/pindel_np_from_vcf.pl b/perl/bin/pindel_np_from_vcf.pl index d3cdd8f..fd80600 100755 --- a/perl/bin/pindel_np_from_vcf.pl +++ b/perl/bin/pindel_np_from_vcf.pl @@ -1,5 +1,5 @@ #!/usr/bin/perl -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/perl/bin/pindel_np_remsample.pl b/perl/bin/pindel_np_remsample.pl index 8b72078..f517944 100755 --- a/perl/bin/pindel_np_remsample.pl +++ b/perl/bin/pindel_np_remsample.pl @@ -1,5 +1,5 @@ #!/usr/bin/perl -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/perl/bin/pindel_vcfSortNsplit.pl b/perl/bin/pindel_vcfSortNsplit.pl index 274df77..fcdee3d 100755 --- a/perl/bin/pindel_vcfSortNsplit.pl +++ b/perl/bin/pindel_vcfSortNsplit.pl @@ -1,5 +1,5 @@ #!/usr/bin/env perl -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/perl/bin/prep_np_release.pl b/perl/bin/prep_np_release.pl index af6b80f..8ee44b9 100755 --- a/perl/bin/prep_np_release.pl +++ b/perl/bin/prep_np_release.pl @@ -1,5 +1,5 @@ #!/usr/bin/perl -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/perl/lib/Sanger/CGP/Pindel.pm b/perl/lib/Sanger/CGP/Pindel.pm index 9c7c0b5..858d636 100644 --- a/perl/lib/Sanger/CGP/Pindel.pm +++ b/perl/lib/Sanger/CGP/Pindel.pm @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/perl/lib/Sanger/CGP/Pindel/Implement.pm b/perl/lib/Sanger/CGP/Pindel/Implement.pm index ce2440a..3db4d69 100644 --- a/perl/lib/Sanger/CGP/Pindel/Implement.pm +++ b/perl/lib/Sanger/CGP/Pindel/Implement.pm @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/perl/lib/Sanger/CGP/Pindel/InputGen.pm b/perl/lib/Sanger/CGP/Pindel/InputGen.pm index 4fd8499..b719abf 100644 --- a/perl/lib/Sanger/CGP/Pindel/InputGen.pm +++ b/perl/lib/Sanger/CGP/Pindel/InputGen.pm @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/perl/lib/Sanger/CGP/Pindel/InputGen/Pair.pm b/perl/lib/Sanger/CGP/Pindel/InputGen/Pair.pm index b01ee04..59c7f4d 100644 --- a/perl/lib/Sanger/CGP/Pindel/InputGen/Pair.pm +++ b/perl/lib/Sanger/CGP/Pindel/InputGen/Pair.pm @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/perl/lib/Sanger/CGP/Pindel/InputGen/PairToPindel.pm b/perl/lib/Sanger/CGP/Pindel/InputGen/PairToPindel.pm index cb850eb..7c8a36d 100644 --- a/perl/lib/Sanger/CGP/Pindel/InputGen/PairToPindel.pm +++ b/perl/lib/Sanger/CGP/Pindel/InputGen/PairToPindel.pm @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/perl/lib/Sanger/CGP/Pindel/InputGen/Read.pm b/perl/lib/Sanger/CGP/Pindel/InputGen/Read.pm index 6721a8e..a1d9da7 100644 --- a/perl/lib/Sanger/CGP/Pindel/InputGen/Read.pm +++ b/perl/lib/Sanger/CGP/Pindel/InputGen/Read.pm @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/perl/lib/Sanger/CGP/Pindel/InputGen/SamHeader.pm b/perl/lib/Sanger/CGP/Pindel/InputGen/SamHeader.pm index b28fde5..bfae6b7 100644 --- a/perl/lib/Sanger/CGP/Pindel/InputGen/SamHeader.pm +++ b/perl/lib/Sanger/CGP/Pindel/InputGen/SamHeader.pm @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/BamUtil.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/BamUtil.pm index ee33c49..8584de3 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/BamUtil.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/BamUtil.pm @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/CombinedRecord.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/CombinedRecord.pm index c8b42d1..a72f4cd 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/CombinedRecord.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/CombinedRecord.pm @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/CombinedRecordGenerator.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/CombinedRecordGenerator.pm index f6ec5b5..55aa479 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/CombinedRecordGenerator.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/CombinedRecordGenerator.pm @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/PindelRecord.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/PindelRecord.pm index f8692a6..bbac5ea 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/PindelRecord.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/PindelRecord.pm @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/PindelRecordParser.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/PindelRecordParser.pm index 37e50e1..57d725f 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/PindelRecordParser.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/PindelRecordParser.pm @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm index e589c99..634d24e 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfCohortConverter.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfCohortConverter.pm index f989cb0..f29e974 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfCohortConverter.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfCohortConverter.pm @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfConverter.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfConverter.pm index e3d139d..4b545bc 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfConverter.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfConverter.pm @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/perl/lib/Sanger/CGP/PindelPostProcessing/AbstractExe.pm b/perl/lib/Sanger/CGP/PindelPostProcessing/AbstractExe.pm index afd42d3..1b1c388 100644 --- a/perl/lib/Sanger/CGP/PindelPostProcessing/AbstractExe.pm +++ b/perl/lib/Sanger/CGP/PindelPostProcessing/AbstractExe.pm @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/perl/lib/Sanger/CGP/PindelPostProcessing/FilterRules.pm b/perl/lib/Sanger/CGP/PindelPostProcessing/FilterRules.pm index 92b8e81..959db1f 100644 --- a/perl/lib/Sanger/CGP/PindelPostProcessing/FilterRules.pm +++ b/perl/lib/Sanger/CGP/PindelPostProcessing/FilterRules.pm @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/perl/lib/Sanger/CGP/PindelPostProcessing/FragmentFilterRules.pm b/perl/lib/Sanger/CGP/PindelPostProcessing/FragmentFilterRules.pm index 1e9f299..abfece5 100644 --- a/perl/lib/Sanger/CGP/PindelPostProcessing/FragmentFilterRules.pm +++ b/perl/lib/Sanger/CGP/PindelPostProcessing/FragmentFilterRules.pm @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/perl/lib/Sanger/CGP/PindelPostProcessing/VcfSoftFlagger.pm b/perl/lib/Sanger/CGP/PindelPostProcessing/VcfSoftFlagger.pm index 3d71f81..dcfe7c2 100644 --- a/perl/lib/Sanger/CGP/PindelPostProcessing/VcfSoftFlagger.pm +++ b/perl/lib/Sanger/CGP/PindelPostProcessing/VcfSoftFlagger.pm @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/perl/util/pairedSplit.pl b/perl/util/pairedSplit.pl index 5c7f754..e464c68 100755 --- a/perl/util/pairedSplit.pl +++ b/perl/util/pairedSplit.pl @@ -1,5 +1,5 @@ #!/usr/bin/env perl -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/python/pindelMmPlots.py b/python/pindelMmPlots.py index 293d62d..efd41bb 100755 --- a/python/pindelMmPlots.py +++ b/python/pindelMmPlots.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # diff --git a/setup.sh b/setup.sh index a345a55..f7ce414 100755 --- a/setup.sh +++ b/setup.sh @@ -1,6 +1,6 @@ #!/bin/bash # -# Copyright (c) 2014-2021 +# Copyright (c) 2014-2021 Genome Research Ltd # # Author: CASM/Cancer IT # From 5c546341e4cc64f147f43285e2656013af7c6c17 Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Fri, 27 Aug 2021 11:29:35 +0100 Subject: [PATCH 36/60] Correct skywalking eyes pattern --- .licenserc.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.licenserc.yaml b/.licenserc.yaml index 2011449..12af0b6 100644 --- a/.licenserc.yaml +++ b/.licenserc.yaml @@ -33,7 +33,7 @@ header: 2009, 2010, 2011, 2012’. pattern: | - Copyright \(c\) \d+ .+ + Copyright \(c\) [-0-9]+ .+ Author: .+ From 5b1db62cbd46f3a493287578c2e69654bdc8b9c1 Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Wed, 8 Sep 2021 13:57:52 +0100 Subject: [PATCH 37/60] consolidate versions --- setup.sh | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/setup.sh b/setup.sh index f7ce414..350285e 100755 --- a/setup.sh +++ b/setup.sh @@ -33,6 +33,7 @@ # need to keep in sync with Dockerfile export VER_CGPVCF="v2.2.1" export VER_VCFTOOLS="0.1.16" +export VER_BLAT="v385" get_file () { # output, source @@ -57,11 +58,6 @@ if [[ $# -eq 2 ]] ; then CGP_PERLLIBS=$2 fi -# ALL tool versions used by opt-build.sh -# need to keep in sync with Dockerfile -export VER_CGPVCF="v2.2.1" -export VER_VCFTOOLS="0.1.16" -export VER_BLAT="v385" # get current directory INIT_DIR=`pwd` From 5e2a44ca65a15b344a1898aab9c6539ff9315ee2 Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Wed, 8 Sep 2021 13:58:12 +0100 Subject: [PATCH 38/60] remove dev comment --- perl/lib/Sanger/CGP/Pindel/Implement.pm | 1 - 1 file changed, 1 deletion(-) diff --git a/perl/lib/Sanger/CGP/Pindel/Implement.pm b/perl/lib/Sanger/CGP/Pindel/Implement.pm index 3db4d69..f9b3112 100644 --- a/perl/lib/Sanger/CGP/Pindel/Implement.pm +++ b/perl/lib/Sanger/CGP/Pindel/Implement.pm @@ -254,7 +254,6 @@ sub concat { my $command = _which('vcf-concat'); $command .= sprintf q{ %s | bgzip -c > %s}, catfile($tmp, 'blat_*/data.vcf'), - #catfile($vcf, 'blat_*.vcf'), $vcf_gz; PCAP::Threaded::external_process_handler(catdir($tmp, 'logs'), ['set -o pipefail', $command], 'concat'); PCAP::Threaded::touch_success(catdir($tmp, 'progress'), 'concat'); From 1ce8000617ee4a2257af2bbedf64a217ab46456a Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Wed, 8 Sep 2021 14:00:39 +0100 Subject: [PATCH 39/60] update swe image, fix up badges --- README.md | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d7254c8..aec2ba9 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,11 @@ cgpPindel contains the Cancer Genome Projects workflow for [Pindel][pindel-core]. -[![cancerit](https://circleci.com/gh/cancerit/cgpPindel.svg?style=svg)](https://circleci.com/gh/cancerit/cgpPindel) +| Master | Develop | +| --------------------------------------------- | ----------------------------------------------- | +| [![Master Badge][circle-master]][circle-base] | [![Develop Badge][circle-develop]][circle-base] | + +[![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/pre-commit/pre-commit) The is a lightly modified version of pindel v2.0 with CGP specific processing for: @@ -84,12 +88,19 @@ Please use [skywalking-eyes](https://github.com/apache/skywalking-eyes). Expected workflow: +Expected workflow: + +```bash +# recent build, change to apache/skywalking-eyes:0.2.0 once released +export DOCKER_IMG=ghcr.io/apache/skywalking-eyes/license-eye +``` + 1. Check state before modifying `.licenserc.yaml`: - - `docker run -it --rm -v $(pwd):/github/workspace apache/skywalking-eyes header check` + - `docker run -it --rm -v $(pwd):/github/workspace $DOCKER_IMG header check` - You should get some 'valid' here, those without a header as 'invalid' 1. Modify `.licenserc.yaml` 1. Apply the changes: - - `docker run -it --rm -v $(pwd):/github/workspace apache/skywalking-eyes header fix` + - `docker run -it --rm -v $(pwd):/github/workspace $DOCKER_IMG header fix` 1. Add/commit changes This is executed in the CI pipeline. @@ -173,14 +184,15 @@ identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, - - - + [cgppindel-wiki]: https://github.com/cancerit/cgpPindel/wiki [cgpvcf-rel]: https://github.com/cancerit/cgpVcf/releases +[circle-base]: https://circleci.com/gh/cancerit/cgpPindel.svg?style=shield +[circle-develop]: https://circleci.com/gh/cancerit/cgpPindel.svg?style=shield&branch=dev%3B +[circle-master]: https://circleci.com/gh/cancerit/cgpPindel.svg?style=shield&branch=master%3B [circle-repo]: https://app.circleci.com/pipelines/github/cancerit/cgpPindel [ds-cgpwgs-git]: https://github.com/cancerit/dockstore-cgpwgs [ds-cgpwxs-git]: https://github.com/cancerit/dockstore-cgpwxs From 378cdc5c32b1b48a6b13bdc2202553003a48da89 Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Wed, 8 Sep 2021 14:01:58 +0100 Subject: [PATCH 40/60] Detail the changes --- CHANGES.md | 108 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 63 insertions(+), 45 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d43d14d..e3a3653 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,108 +1,121 @@ # CHANGES +## NEXT + +- Adds code to allow single sample processing with more accurate VAF calculations (via BLAT) +- Status of new scripts, "pre-release" indicates defaults and CLI may change: + - stable + - pindelCohort.pl + - pindel_blat_vaf.pl + - pre-release + - pindelCohort_to_vcf.pl + - pindel_vcfSortNsplit.pl + - pindelCohortMerge.pl + - pindelCohortVafFill.pl + - pindelCohortVafSplit.pl + - pindelCohortVafSliceFill.pl +- Switch license management to skywalking-eyes. + ## 3.5.0 -* Update to core pindel algorithm to allow complex DI events to have longer inserted sequence than deleted - * Masking real events +- Update to core pindel algorithm to allow complex DI events to have longer inserted sequence than deleted + - Masking real events ## 3.4.1 -* Updated Dockerfile to use pcap-core 5.4.0 - htslib/samtools 1.11 +- Updated Dockerfile to use pcap-core 5.4.0 - htslib/samtools 1.11 ## 3.4.0 -* Updated Dockerfile to use pcap-core 5.2.2 -* Modified setup script to use build/*.sh +- Updated Dockerfile to use pcap-core 5.2.2 +- Modified setup script to use build/\*.sh ## 3.3.0 -* I/O hardening, see [milestone 3](https://github.com/cancerit/cgpPindel/milestone/3) +- I/O hardening, see [milestone 3](https://github.com/cancerit/cgpPindel/milestone/3) ## 3.2.2 -* Handle Input files that may have no reads at all, specifically an issue when generating a normal panel. +- Handle Input files that may have no reads at all, specifically an issue when generating a normal panel. ## 3.2.1 -* Added Dockerfile and docker documentation +- Added Dockerfile and docker documentation ## 3.2.0 -* Tabix search for high depth/excluded regions now performed in memory using IntervalTrees - * Reduces runtime of input step by ~50% - * Improved disk access profile - * Zero impact on results +- Tabix search for high depth/excluded regions now performed in memory using IntervalTrees + - Reduces runtime of input step by ~50% + - Improved disk access profile + - Zero impact on results ## 3.1.2 -* 3.0.5 introduced species parsing bug causing single word species names to be invalid. +- 3.0.5 introduced species parsing bug causing single word species names to be invalid. ## 3.1.1 -* Fix regression - ability to cope with chromosomes with no events. +- Fix regression - ability to cope with chromosomes with no events. ## 3.1.0 -* Incorporates updated pindel which improves sensitivity -* Internally interpret QCFAIL to determine if whole pair fails +- Incorporates updated pindel which improves sensitivity +- Internally interpret QCFAIL to determine if whole pair fails ## 3.0.6 -* Fixed version tag +- Fixed version tag ## 3.0.5 -* Handles species names with spaces in it -* modified checks for species,assembly and checksum +- Handles species names with spaces in it +- modified checks for species,assembly and checksum ## 3.0.4 -* Output bug for pindel BAM/CRAM corrected. When more than 1 chr in output files had no reads. +- Output bug for pindel BAM/CRAM corrected. When more than 1 chr in output files had no reads. ## 3.0.3 -* Changes to how germline filter determined resulted in dummy germline bed file not being generated as previously. -* This release reinstates the old behaviour. +- Changes to how germline filter determined resulted in dummy germline bed file not being generated as previously. +- This release reinstates the old behaviour. ## 3.0.2 -* Correct example rule files for *Fragment.lst files to use FFnnn filter types +- Correct example rule files for \*Fragment.lst files to use FFnnn filter types ## 3.0.1 -* Update tabix calls to directly use query_full (solves GRCh38 contig name issues). +- Update tabix calls to directly use query_full (solves GRCh38 contig name issues). ## 3.0.0 -* Germline bed file is now merged for adjacent regions (#31) -* More compressed intermediate files (#55) -* Change to `Const::Fast` where appropriate (#41) -* Removed TG VG from genotype. - * Readgroups are always variable, often 1 in data from last few years - * Not used by our filters. -* Supports BAM/CRAM inputs -* Output will be aligned with inputs - * bam vs cram - * bai vs csi -* Although ground work for csi input/output has been done `Bio::DB::HTS` doesn't support csi indexed input yet. - * Created our own fork at [`cancerit/Bio::DB::HTS`][cancerit-biodbhts] so that this could be enabled. - * You will need to install this manually or use one of our images for this functionallity. - * [dockstore-cgpwxs][ds-cgpwxs-git] - * [dockstore-cgpwxs][ds-cgpwgs-git] +- Germline bed file is now merged for adjacent regions (#31) +- More compressed intermediate files (#55) +- Change to `Const::Fast` where appropriate (#41) +- Removed TG VG from genotype. + - Readgroups are always variable, often 1 in data from last few years + - Not used by our filters. +- Supports BAM/CRAM inputs +- Output will be aligned with inputs + - bam vs cram + - bai vs csi +- Although ground work for csi input/output has been done `Bio::DB::HTS` doesn't support csi indexed input yet. + - Created our own fork at [`cancerit/Bio::DB::HTS`][cancerit-biodbhts] so that this could be enabled. + - You will need to install this manually or use one of our images for this functionallity. + - [dockstore-cgpwxs][ds-cgpwxs-git] + - [dockstore-cgpwxs][ds-cgpwgs-git] -[cancerit-biodbhts]: https://github.com/cancerit/Bio-DB-HTS/releases/tag/v2.10-rc1 -[ds-cgpwxs-git]: https://github.com/cancerit/dockstore-cgpwxs -[ds-cgpwgs-git]: https://github.com/cancerit/dockstore-cgpwgs ## 2.2.5 -* Update tabix->query to tabix->query_full +- Update tabix->query to tabix->query_full ## 2.2.4 -* Force sorting of FILTER field to make records easier to diff. -* Fix sorting of final VCF to handle events with same start better when using comparison tools +- Force sorting of FILTER field to make records easier to diff. +- Fix sorting of final VCF to handle events with same start better when using comparison tools ## 2.2.3 @@ -180,3 +193,8 @@ Found 15321 SNPs common to both files. Found 0 SNPs only in main file. Found 0 SNPs only in second file. After +``` + +[cancerit-biodbhts]: https://github.com/cancerit/Bio-DB-HTS/releases/tag/v2.10-rc1 +[ds-cgpwgs-git]: https://github.com/cancerit/dockstore-cgpwgs +[ds-cgpwxs-git]: https://github.com/cancerit/dockstore-cgpwxs From 31b8b7e841d4de6d509b6e1336ee1906d8d91e86 Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Wed, 6 Oct 2021 14:03:23 +0100 Subject: [PATCH 41/60] Iterative improvement to calls --- .dockerignore | 1 + perl/bin/pindel_blat_vaf.pl | 2 ++ perl/lib/Sanger/CGP/Pindel/Implement.pm | 15 +++++++---- .../CGP/Pindel/OutputGen/VcfBlatAugment.pm | 27 ++++--------------- 4 files changed, 18 insertions(+), 27 deletions(-) diff --git a/.dockerignore b/.dockerignore index 76bab9e..035e922 100644 --- a/.dockerignore +++ b/.dockerignore @@ -14,3 +14,4 @@ /install_tmp /.circleci /*.code-workspace +/tmp diff --git a/perl/bin/pindel_blat_vaf.pl b/perl/bin/pindel_blat_vaf.pl index 2f178ee..1006f5a 100755 --- a/perl/bin/pindel_blat_vaf.pl +++ b/perl/bin/pindel_blat_vaf.pl @@ -52,6 +52,7 @@ sam => $options->{align}, hts_files => $options->{hts}, outpath => $options->{outpath}, + debug => $options->{debug}, ); $augment->output_header; @@ -126,6 +127,7 @@ =head1 SYNOPSIS -output -o Directory for VCF output (gz compressed) and colocated sample bams Other: + -debug -d Turn on additional outputs. -help -h Brief help message. -man -m Full documentation. -version -v Prints the version number. diff --git a/perl/lib/Sanger/CGP/Pindel/Implement.pm b/perl/lib/Sanger/CGP/Pindel/Implement.pm index f9b3112..be65c0b 100644 --- a/perl/lib/Sanger/CGP/Pindel/Implement.pm +++ b/perl/lib/Sanger/CGP/Pindel/Implement.pm @@ -780,15 +780,20 @@ sub merge_vaf_bams { } # list of bams to merge lines_to_file($in_file_list, [$options->{secondary_hts}->[$arr_idx], @split_bams]); - my $command = _which('samtools'); + my $merged_bam = catfile($options->{output}, sprintf('%s.vaf.bam', $sample)); + my $sort_prefix = catfile($sort_tmp, 'samsort'); + my $sort_cleanup = sprintf 'rm -rf %s.*.bam', $sort_prefix; + my $sam_merge = _which('samtools'); # needs to attempt to clean up duplicate reads - $command .= sprintf q{ merge --output-fmt SAM -b %s - | pee 'grep ^@' 'grep -v ^@ | sort -S 2G -T %s | uniq' | samtools view -u - | samtools sort -m 2G -T %s -o %s -}, + $sam_merge .= sprintf q{ merge --output-fmt SAM -b %s - | pee 'grep ^@' 'grep -v ^@ | sort -S 2G -T %s | uniq' | samtools view -u - | samtools sort -m 2G -T %s -o %s -}, $in_file_list, # merge filelist $sort_tmp, # sort tmptfile - catfile($sort_tmp, 'samsort'), - catfile($options->{output}, sprintf('%s.vaf.bam', $sample)); # outfile + $sort_prefix, + $merged_bam; # outfile + my $sam_idx = _which('samtools'); + $sam_idx .= sprintf q{ index %s}, $merged_bam; - PCAP::Threaded::external_process_handler(catdir($tmp, 'logs'), $command, $index); + PCAP::Threaded::external_process_handler(catdir($tmp, 'logs'), [$sort_cleanup, $sam_merge, $sam_idx], $index); remove_tree($sort_tmp); PCAP::Threaded::touch_success(catdir($tmp, 'progress'), $index); } diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm index 634d24e..e72bfc2 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm @@ -54,10 +54,9 @@ const my $V_FMT => 8; const my $V_GT_START => 9; const my $READS_AND_BLAT => q{bash -c 'set -o pipefail ; samtools view -uF 3840 %s %s | samtools fasta - > %s && blat -t=dna -q=dna -noTrimA -minIdentity=95 -noHead -out=psl %s %s %s && pslPretty -long -axt %s %s %s /dev/stdout'}; # returns +ve and then -ve results -const my $SAM_DEPTH_PN => q{bash -c "set -o pipefail ; samtools view -uF 3844 %s %s | pee 'samtools view -c -F 16 -' 'samtools view -c -f 16 -'"}; const my $LOCI_FMT => '%s:%d-%d'; const my $PAD_EVENT => 3; -const my $MAPPED_RL_MULT => 0.6; +const my $MAPPED_RL_MULT => 0.9; 1; @@ -72,6 +71,7 @@ sub new{ ofh => $args{ofh}, # vcf hts_files => $args{hts_files}, fill_in => $args{fill_in}, + debug => $args{debug} || 0, }; bless $self, $class; @@ -176,7 +176,8 @@ sub _buffer_sizes { my $b = PCAP::Bam::Bas->new($self->{hts}->{$hts_sample}->hts_path.'.bas'); my $sample; for my $rg($b->read_groups) { - my $m_sd = int ($b->get($rg, 'mean_insert_size') + ($b->get($rg, 'insert_size_sd') * $SD_MULT)); + #my $m_sd = int ($b->get($rg, 'mean_insert_size') + ($b->get($rg, 'insert_size_sd') * $SD_MULT)); + my $m_sd = int ($b->get($rg, 'mean_insert_size') * 5); $max_ins = $m_sd if($m_sd > $max_ins); my $tmp_max = max ($b->get($rg, 'read_length_r1'), $b->get($rg, 'read_length_r2')); my $tmp_min = min ($b->get($rg, 'read_length_r1'), $b->get($rg, 'read_length_r2')); @@ -375,11 +376,7 @@ sub blat_reads { my ($wtp, $wtn, $mtp, $mtn, $wt_bmm, $mt_bmm) = $self->psl_axt_parser(\$c_out, $v_h, $sample); my ($wtm, $mtm) = (q{.}, q{.}); my $wtr = $wtp + $wtn; - if($wtr == 0) { - ($wtp, $wtn) = $self->sam_depth($v_h, $sample); - $wtr = $wtp + $wtn; - } - else { + if($wtr > 0) { $wtm = sprintf("%.3f", $wt_bmm / $wtr); } my $mtr = $mtp+$mtn; @@ -509,20 +506,6 @@ sub sam_record { printf {$self->{sfh}->{$sample}} "%s\n", join "\t", $qname, $flag, $v_h->{CHROM}, $pos, 60, $cigar, '*', 0, 0, $seq, '*'; } -sub sam_depth { - my ($self, $v_h, $sample) = @_; - my $mid_point = int ($v_h->{RS} + (($v_h->{RE} - $v_h->{RS})*0.5)); - my $read_search = sprintf $LOCI_FMT, $v_h->{CHROM}, $mid_point, $mid_point; - my $c_samcount = sprintf $SAM_DEPTH_PN, $self->{hts}->{$sample}->hts_path, $read_search; - my ($c_out, $c_err, $c_exit) = capture { system($c_samcount); }; - if($c_exit) { - warn "An error occurred while executing $c_samcount\n"; - warn "\tERROR$c_err\n"; - exit $c_exit; - } - return (split /\n/, $c_out); -} - sub sam_to_bam { my ($self) = @_; for my $sample(@{$self->{vcf_sample_order}}) { From 12c860075e468aca24d4faf44ea4a5c906de564e Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Wed, 6 Oct 2021 14:04:14 +0100 Subject: [PATCH 42/60] Some simple util scripts for generating data grids --- perl/util/cohortVcfToGrids.pl | 58 ++++++++++++++++++++++++++++++++++ perl/util/vafCorrectToGrids.pl | 56 ++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100755 perl/util/cohortVcfToGrids.pl create mode 100755 perl/util/vafCorrectToGrids.pl diff --git a/perl/util/cohortVcfToGrids.pl b/perl/util/cohortVcfToGrids.pl new file mode 100755 index 0000000..d805970 --- /dev/null +++ b/perl/util/cohortVcfToGrids.pl @@ -0,0 +1,58 @@ +#!/usr/bin/perl + +use strict; +use IO::Uncompress::Gunzip qw($GunzipError) ; +use List::Util qw(sum); + +my ($combined_vcf, $exclude_sample, $output) = @ARGV; +# use a basic bed style output so we can intersect etc + +my ($wtp, $wtn, $mtp, $mtn) = (5,6,8,9); + +my $z = new IO::Uncompress::Gunzip $combined_vcf, MultiStream => 1 or die "IO::Uncompress::Gunzip failed: $GunzipError\n"; +open my $O_DPTH, '>', $output.'.depth' or die "Failed to create $output.depth"; +open my $O_ALT, '>', $output.'.alt' or die "Failed to create $output.alt"; +my @sample_order; +while (my $l = <$z>) { + next if $l =~ m/^##/; + chomp $l; + my @data = split "\t", $l; + my ($chr, $pos, $ref, $alt, $gt) = @data[0,1,3,4,8]; + my @samples = @data[9..$#data]; + # header junk + if($chr eq '#CHROM'){ + @sample_order = @samples; + print $O_DPTH 'ID'; + print $O_ALT 'ID'; + for my $idx (0..$#samples) { + if($sample_order[$idx] eq $exclude_sample) { + next; + } + print $O_DPTH "\t".$sample_order[$idx]; + print $O_ALT "\t".$sample_order[$idx]; + } + print $O_DPTH "\n"; + print $O_ALT "\n"; + + next; + } + # body + $chr =~ s/^chr//; + my $id = sprintf '%s_%d_%s_%s', $chr, $pos, $ref, $alt; + my @total_depth = ( $id ); + my @alt_depth = ( $id ); + for my $idx (0..$#samples) { + if($sample_order[$idx] eq $exclude_sample) { + next; + } + my @gt_data = split ':', $samples[$idx]; + push @total_depth, sum (@gt_data[$wtp, $wtn, $mtp, $mtn]); + push @alt_depth, sum (@gt_data[$mtp, $mtn]); + #print "$gt : $samples[$idx] : $total_depth : $mut_depth\n"; + } + print $O_DPTH join("\t", @total_depth)."\n"; + print $O_ALT join("\t", @alt_depth)."\n"; +} +close $O_DPTH; +close $O_ALT; +close $z; diff --git a/perl/util/vafCorrectToGrids.pl b/perl/util/vafCorrectToGrids.pl new file mode 100755 index 0000000..ea65017 --- /dev/null +++ b/perl/util/vafCorrectToGrids.pl @@ -0,0 +1,56 @@ +#!/usr/bin/perl + +use strict; + +my ($vaf_correct, $exclude_sample, $output) = @ARGV; + +open my $O_VC, '<', $vaf_correct; + +my $header = <$O_VC>; +chomp $header; +my @head_items = split "\t", $header; +my ($chr, $pos, $ref, $alt) = @head_items[2,3,4,5]; +my %mtr_by_sample; +for my $i(6..$#head_items) { + if($head_items[$i] =~ m/_MTR$/) { + my $sample = $head_items[$i]; + $sample =~ s/_MTR$//; + next if($sample eq $exclude_sample); + $mtr_by_sample{$sample} = $i; + } +} + +my @samples = sort keys %mtr_by_sample; +my (@mtr_cols, @wtr_cols); +for my $s(@samples) { + push @mtr_cols, $mtr_by_sample{$s}; + push @wtr_cols, $mtr_by_sample{$s} + 1; +} + + +open my $O_DPTH, '>', $output.'.depth' or die "Failed to create $output.depth"; +open my $O_ALT, '>', $output.'.alt' or die "Failed to create $output.alt"; + +print $O_DPTH join("\t", "ID", @samples)."\n"; +print $O_ALT join("\t", "ID", @samples)."\n"; + +while(my $l = <$O_VC>) { + chomp $l; + my @row = split "\t", $l; + my ($chr, $pos, $ref, $alt) = @row[2,3,4,5]; + $chr =~ s/^chr//; + my $id = sprintf '%s_%d_%s_%s', $chr, $pos, $ref, $alt; + my @total_depth; + my @alt_depth; + for my $i(0..$#mtr_cols) { + # same length (or broken) + push @total_depth, $row[$mtr_cols[$i]] + $row[$wtr_cols[$i]]; + push @alt_depth, $row[$mtr_cols[$i]]; + } + print $O_DPTH join("\t", $id, @total_depth)."\n"; + print $O_ALT join("\t", $id, @alt_depth)."\n"; +} + +close $O_DPTH; +close $O_ALT; +close $O_VC; From ab98c3bf91a9ef6bec14bb485abebaadcd41fb77 Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Wed, 6 Oct 2021 14:45:10 +0100 Subject: [PATCH 43/60] Faster blats due to reduced read parsing when multiple hits at same loci (common) --- .../CGP/Pindel/OutputGen/VcfBlatAugment.pm | 34 +++++++++++++------ perl/t/vcfBlatAugment.t | 6 ++-- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm index e72bfc2..da5fe31 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm @@ -35,7 +35,7 @@ use Capture::Tiny qw(capture); use Const::Fast qw(const); use File::Basename; use File::Spec::Functions; -use File::Temp qw(tempfile); +use File::Temp qw(tempfile tempdir); use IO::Compress::Gzip qw(:constants gzip $GzipError); use List::Util qw(min max); @@ -52,7 +52,8 @@ use Sanger::CGP::Vcf::VcfUtil; const my $SD_MULT => 2; const my $V_FMT => 8; const my $V_GT_START => 9; -const my $READS_AND_BLAT => q{bash -c 'set -o pipefail ; samtools view -uF 3840 %s %s | samtools fasta - > %s && blat -t=dna -q=dna -noTrimA -minIdentity=95 -noHead -out=psl %s %s %s && pslPretty -long -axt %s %s %s /dev/stdout'}; +const my $READS_ONLY => q{bash -c 'set -o pipefail; samtools view -uF 3840 %s %s | samtools fasta - > %s'}; +const my $BLAT_ONLY => q{bash -c 'blat -t=dna -q=dna -noTrimA -minIdentity=95 -noHead -out=psl %s %s %s && pslPretty -long -axt %s %s %s /dev/stdout'}; # returns +ve and then -ve results const my $LOCI_FMT => '%s:%d-%d'; const my $PAD_EVENT => 3; @@ -299,16 +300,24 @@ sub to_data_hash { sub process_records { my $self = shift; my $fh = $self->{ofh}; + my $readtmp_dir; + my $last_chr_pos = q{.}; while(my $v_d = $self->{vcf}->next_data_array) { + my $this_c_p = sprintf "%s:%d", $v_d->[0], $v_d->[1]; + if($last_chr_pos ne $this_c_p) { + # we use very large search range, so if the start pos doesn't change don't want to reparse reads + $readtmp_dir = tempdir( CLEANUP => 1 ); + $last_chr_pos = $this_c_p; + } $v_d->[$V_FMT] .= $self->{fmt_ext} unless($self->{fill_in}); - $self->blat_record($v_d); + $self->blat_record($v_d, $readtmp_dir); printf $fh "%s\n", join "\t", @{$v_d}; } $self->_close_sams } sub blat_record { - my ($self, $v_d) = @_; + my ($self, $v_d, $readtmp_dir) = @_; my $v_h = $self->to_data_hash($v_d); # now attempt the blat stuff @@ -329,7 +338,7 @@ sub blat_record { if($self->{fill_in} && $v_d->[$gt_pos] ne q{.}) { next; } - my $gt_set = $self->blat_reads($v_h, $file_target, $sample); + my $gt_set = $self->blat_reads($v_h, $file_target, $readtmp_dir, $sample); if($v_d->[$gt_pos] eq q{.}) { $v_d->[$gt_pos] = join q{:}, './.:.:.:.:.', @{$gt_set}; } @@ -350,14 +359,18 @@ sub read_ranges { } sub blat_reads { - my ($self, $v_h, $file_target, $sample) = @_; - # setup the temp files - my ($fh_query, $file_query) = tempfile( SUFFIX => '.fa', UNLINK => 1); - close $fh_query or die "Failed to close $file_query (query reads)"; + my ($self, $v_h, $file_target, $readtmp_dir, $sample) = @_; + my $file_query = sprintf '%s/%s.fa', $readtmp_dir, $sample; + # setup the temp file my ($fh_psl, $file_psl) = tempfile( SUFFIX => '.psl', UNLINK => 1); close $fh_psl or die "Failed to close $file_psl (psl output)"; - my $c_blat = sprintf $READS_AND_BLAT, $self->{hts}->{$sample}->hts_path, $self->read_ranges($v_h, $sample), $file_query, $file_target, $file_query, $file_psl, $file_psl, $file_target, $file_query; + if(! -e $file_query) { + my $c_reads = sprintf $READS_ONLY, $self->{hts}->{$sample}->hts_path, $self->read_ranges($v_h, $sample), $file_query; + my ($r_out, $r_err, $r_exit) = capture { system([0], $c_reads); }; + } + + my $c_blat = sprintf $BLAT_ONLY, $file_target, $file_query, $file_psl, $file_psl, $file_target, $file_query; my ($c_out, $c_err, $c_exit) = capture { system([0,255], $c_blat); }; if($c_exit == 255 && $c_err =~ m/processed 0 reads/ms) { # No reads found @@ -370,7 +383,6 @@ sub blat_reads { } # tempfile unlink only does it on shutdown when used in this way - unlink $file_query; unlink $file_psl; my ($wtp, $wtn, $mtp, $mtn, $wt_bmm, $mt_bmm) = $self->psl_axt_parser(\$c_out, $v_h, $sample); diff --git a/perl/t/vcfBlatAugment.t b/perl/t/vcfBlatAugment.t index d9a3c23..1678d96 100644 --- a/perl/t/vcfBlatAugment.t +++ b/perl/t/vcfBlatAugment.t @@ -41,21 +41,21 @@ subtest 'Header checks' => sub { subtest 'Simple Deletion checks' => sub { my $vba = new_vba(catdir($DATA, 'D')); my @tmp = @{$DATA_ARR_D}; - $vba->blat_record(\@tmp); + $vba->blat_record(\@tmp, tempdir(CLEANUP => 1)); is_deeply(\@tmp, $RES_ARR_D); }; subtest 'Simple Insertion checks' => sub { my $vba = new_vba(catdir($DATA, 'SI')); my @tmp = @{$DATA_ARR_SI}; - $vba->blat_record(\@tmp); + $vba->blat_record(\@tmp, tempdir(CLEANUP => 1)); is_deeply(\@tmp, $RES_ARR_SI); }; subtest 'Complex event checks' => sub { my $vba = new_vba(catdir($DATA, 'DI')); my @tmp = @{$DATA_ARR_DI}; - $vba->blat_record(\@tmp); + $vba->blat_record(\@tmp, tempdir(CLEANUP => 1)); print join q{ }, @tmp; is_deeply(\@tmp, $RES_ARR_DI); }; From c1afb9e7ad367fb96d69eb118c38ac75cee7c22e Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Wed, 6 Oct 2021 14:59:37 +0100 Subject: [PATCH 44/60] missing licenses --- perl/util/cohortVcfToGrids.pl | 29 +++++++++++++++++++++++++++++ perl/util/vafCorrectToGrids.pl | 29 +++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/perl/util/cohortVcfToGrids.pl b/perl/util/cohortVcfToGrids.pl index d805970..3243693 100755 --- a/perl/util/cohortVcfToGrids.pl +++ b/perl/util/cohortVcfToGrids.pl @@ -1,4 +1,33 @@ #!/usr/bin/perl +# Copyright (c) 2014-2021 Genome Research Ltd +# +# Author: CASM/Cancer IT +# +# This file is part of cgpPindel. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# use strict; use IO::Uncompress::Gunzip qw($GunzipError) ; diff --git a/perl/util/vafCorrectToGrids.pl b/perl/util/vafCorrectToGrids.pl index ea65017..e8f1e6b 100755 --- a/perl/util/vafCorrectToGrids.pl +++ b/perl/util/vafCorrectToGrids.pl @@ -1,4 +1,33 @@ #!/usr/bin/perl +# Copyright (c) 2014-2021 Genome Research Ltd +# +# Author: CASM/Cancer IT +# +# This file is part of cgpPindel. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# use strict; From 4f6b59e1fc47f77e4fb39749095c60715da8027c Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Wed, 10 Nov 2021 10:47:49 +0000 Subject: [PATCH 45/60] current state --- perl/bin/pindelCohortMerge.pl | 5 +- perl/bin/pindelCohortVafSliceFill.pl | 4 +- .../CGP/Pindel/OutputGen/VcfBlatAugment.pm | 117 +++++++++++++++--- perl/t/vcfBlatAugment.t | 6 +- 4 files changed, 107 insertions(+), 25 deletions(-) diff --git a/perl/bin/pindelCohortMerge.pl b/perl/bin/pindelCohortMerge.pl index 35c5cef..dd9af59 100755 --- a/perl/bin/pindelCohortMerge.pl +++ b/perl/bin/pindelCohortMerge.pl @@ -98,7 +98,8 @@ sub records { $mtp = 0 if($mtp eq q{.}); $mtn = 0 if($mtn eq q{.}); $samples_with_min_vaf++ if($vaf >= $min_vaf); - $samples_with_min_blat++ if($mtp >= $min_blat && $mtn >= $min_blat); + # $samples_with_min_blat++ if($mtp >= $min_blat && $mtn >= $min_blat); + $samples_with_min_blat++ if($mtp >= 1 && $mtn >= 1 && $mtp + $mtn >= $min_blat); } else { $row .= "\t."; @@ -294,7 +295,7 @@ =head1 SYNOPSIS -np -n Normal panel gff3 file - omit if no filtering required. -mnps -s Minimum normal panel samples required to exclude [default: 1] -control -c Exclude events where this sample has calls. - -blat -b Exclude events where no sample has MTP >= N && MTN >= N [default: 4] + -blat -b Exclude events where no sample has MTP+MTN >= N (1 reads must be present in each direction) [default: 4] Other: -help -h Brief help message. diff --git a/perl/bin/pindelCohortVafSliceFill.pl b/perl/bin/pindelCohortVafSliceFill.pl index 0d5e7f0..4dcd306 100755 --- a/perl/bin/pindelCohortVafSliceFill.pl +++ b/perl/bin/pindelCohortVafSliceFill.pl @@ -118,8 +118,8 @@ =head1 SYNOPSIS Required parameters: -ref -r File path to the reference file used to provide the coordinate system. -input -i VCF file to read in. - -output -o Directory for VCF output (gz compressed) and colocated sample bams - -data -d File containing list of BWA mappingfiles for all samples used in "-input" + -output -o Directory for VCF output (gz compressed) and collocated sample bams + -data -d File containing list of BWA mapping files for all samples used in "-input" - format: one BWA bam/cram file per line, expects co-located *.bai Other: diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm index da5fe31..134aaeb 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm @@ -34,6 +34,7 @@ use autodie qw(:all); use Capture::Tiny qw(capture); use Const::Fast qw(const); use File::Basename; +use File::Path qw(remove_tree); use File::Spec::Functions; use File::Temp qw(tempfile tempdir); use IO::Compress::Gzip qw(:constants gzip $GzipError); @@ -57,6 +58,7 @@ const my $BLAT_ONLY => q{bash -c 'blat -t=dna -q=dna -noTrimA -minIdentity=95 -n # returns +ve and then -ve results const my $LOCI_FMT => '%s:%d-%d'; const my $PAD_EVENT => 3; +const my $LARGE_D => 100; const my $MAPPED_RL_MULT => 0.9; 1; @@ -200,7 +202,8 @@ sub _buffer_sizes { } $self->{min_rl} = $min_rl; $self->{max_insert} = $max_ins; - $self->{target_pad} = $max_rl; + #$self->{target_pad} = $max_rl; + $self->{target_pad} = $max_ins; # as expanded search space we need to expan the match space return 1; } @@ -210,7 +213,7 @@ sub _hts { my $tmp = Bio::DB::HTS->new(-bam => $hts, -fasta => $self->{ref}); my $sample = $self->_sample_from_hts($tmp); if(exists $self->{hts}->{$sample}) { - die sprintf "ERROR: More than one BAM/CRAM file for sample %s, %s vs %s\n", $sample, $self->{hts}->{$sample}->hts_path, $hts->hts_path; + die sprintf "ERROR: More than one BAM/CRAM file for sample %s, %s vs %s\n", $sample, $self->{hts}->{$sample}->hts_path, $hts; } $self->{hts}->{$sample} = $tmp; } @@ -300,19 +303,22 @@ sub to_data_hash { sub process_records { my $self = shift; my $fh = $self->{ofh}; - my $readtmp_dir; + my $readtmp_dir = tempdir( CLEANUP => 0 ); # doesn't clear as you would expect my $last_chr_pos = q{.}; while(my $v_d = $self->{vcf}->next_data_array) { my $this_c_p = sprintf "%s:%d", $v_d->[0], $v_d->[1]; if($last_chr_pos ne $this_c_p) { # we use very large search range, so if the start pos doesn't change don't want to reparse reads - $readtmp_dir = tempdir( CLEANUP => 1 ); + remove_tree($readtmp_dir, { keep_root => 1 }); $last_chr_pos = $this_c_p; } $v_d->[$V_FMT] .= $self->{fmt_ext} unless($self->{fill_in}); $self->blat_record($v_d, $readtmp_dir); printf $fh "%s\n", join "\t", @{$v_d}; } + if(-d $readtmp_dir) { + remove_tree($readtmp_dir); + } $self->_close_sams } @@ -365,20 +371,32 @@ sub blat_reads { my ($fh_psl, $file_psl) = tempfile( SUFFIX => '.psl', UNLINK => 1); close $fh_psl or die "Failed to close $file_psl (psl output)"; + my $c_reads = sprintf $READS_ONLY, $self->{hts}->{$sample}->hts_path, $self->read_ranges($v_h, $sample), $file_query; if(! -e $file_query) { - my $c_reads = sprintf $READS_ONLY, $self->{hts}->{$sample}->hts_path, $self->read_ranges($v_h, $sample), $file_query; my ($r_out, $r_err, $r_exit) = capture { system([0], $c_reads); }; } my $c_blat = sprintf $BLAT_ONLY, $file_target, $file_query, $file_psl, $file_psl, $file_target, $file_query; my ($c_out, $c_err, $c_exit) = capture { system([0,255], $c_blat); }; - if($c_exit == 255 && $c_err =~ m/processed 0 reads/ms) { + if($c_exit == 255 && ($c_err =~ m/processed 0 reads/ms || $c_err =~ m/End of file reading 4 bytes/ms)) { # No reads found $c_exit = 0; } if($c_exit) { - warn "An error occurred while executing $c_blat\n"; - warn "\tERROR$c_err\n"; + warn "An error occurred while executing: $c_blat\n"; + warn "\tERROR: $c_err\n"; + warn "\tECODE: $c_exit\n"; + warn "Read command: $c_reads\n"; + warn "DATA BLOCK\n"; + warn "Target:\n"; + my ($t_out, $t_err, $t_exit) = capture { system("cat $file_target"); }; + warn $t_out; + warn "Query:\n"; + ($t_out, $t_err, $t_exit) = capture { system("cat $file_query"); }; + warn $t_out; + warn "PSL:\n"; + ($t_out, $t_err, $t_exit) = capture { system("cat $file_psl"); }; + warn $t_out; exit $c_exit; } @@ -419,6 +437,11 @@ sub psl_axt_parser { my %type_strand; my %bmm_sums; + my @ref_reads; + my $is_del = 0; + if(length $v_h->{change_ref} > length $v_h->{change_alt}) { + $is_del = 1; + } # sort keys for consistency READ: for my $read(sort keys %reads) { # get the alignment with the highest score @@ -428,9 +451,14 @@ sub psl_axt_parser { next READ; } my $record = $records[0]; + my $ref_or_alt = $record->[0]; + if($is_del == 1 && $ref_or_alt eq 'REF') { + # see block at end of this function + push @ref_reads, $record; + } if($self->parse_axt_event($v_h, $record, $sample) == 1) { - $type_strand{$record->[0].$record->[6]} += 1; - $bmm_sums{$record->[0]} += bmm($record->[8], $record->[9]); + $type_strand{$ref_or_alt.$record->[6]} += 1; + $bmm_sums{$ref_or_alt} += bmm($record->[8], $record->[9]); } next READ; # remaining items are worse alignments } @@ -440,17 +468,60 @@ sub psl_axt_parser { my $mtp = $type_strand{'ALT+'} || 0; my $mtn = $type_strand{'ALT-'} || 0; + # only relevant for deletions + if($is_del == 1 && $wtp == 0 && $wtn == 0 && $v_h->{RE} - $v_h->{RS} > $LARGE_D) { + # if a deletion is large it can cause no REF depth as impossible for a read to span the ends of the event. + # rather than specifying a cutoff we rely on the data to drive this + my $add_bmb_sum; + ($wtp, $wtn, $add_bmb_sum) = $self->parse_axt_del_ref($v_h, \@ref_reads, $sample); + $bmm_sums{'REF'} += $add_bmb_sum; + } + return ($wtp, $wtn, $mtp, $mtn, $bmm_sums{REF}, $bmm_sums{ALT}); } -sub bmm { - my ($a, $b) = @_; # need a copy of the strings anyway - my $len = length $a; - my $diffs = 0; - for(0..($len-1)) { - $diffs++ if(chop $a ne chop $b); +sub parse_axt_del_ref { + my ($self, $v_h, $records, $sample) = @_; + # Need to return + # pos reads + # neg reads + # mismatch fractions (via bmm) + # Augment $self->sam_record($v_h, $rec, $sample); + + # some rules: + # - this shouldn't be getting called if the event spans both ends so can assume reads at each end can be saved without a check + # - require near perfect match to ref, but reads that get here are known NOT to have a better ALT mapping + + my ($wtp, $wtn, $bmm_sum) = (0,0,0); + my $change_len = $v_h->{change_pos_high} - $v_h->{change_pos_low}; + my $mid_point = $v_h->{change_pos_low} + int($change_len/2); + for my $rec(@{ $records }) { + my ($t_name, $t_start, $t_end, $q_name, $q_start, $q_end, $strand, $score, $t_seq, $q_seq) = @{ $rec }; + if( + ($t_start < $v_h->{change_pos_low} && $t_end > $v_h->{change_pos_low}) + || + ($t_start < $v_h->{change_pos_high} && $t_end > $v_h->{change_pos_high}) + #($t_start < $v_h->{change_pos_low} && $t_end > $v_h->{change_pos_low}) + ) { + if($strand eq '+') { + $wtp += 1; + } + else { + $wtn += 1; + } + $bmm_sum += bmm($t_seq, $q_seq); + $self->sam_record($v_h, $rec, $sample); + } } - return $diffs/$len; + # we've counted 2 locations so can't just return the full value + # this is under review + if($wtp > 0) { + $wtp = int($wtp/2) + 1; + } + if($wtn > 0) { + $wtn = int($wtn/2) + 1; + } + return ($wtp, $wtn, $bmm_sum); } sub parse_axt_event { @@ -486,6 +557,16 @@ sub parse_axt_event { return $retval; } +sub bmm { + my ($a, $b) = @_; # need a copy of the strings anyway + my $len = length $a; + my $diffs = 0; + for(0..($len-1)) { + $diffs++ if(chop $a ne chop $b); + } + return $diffs/$len; +} + sub sam_record { my($self, $v_h, $rec, $sample) = @_; @@ -525,7 +606,7 @@ sub sam_to_bam { my $bam = $self->{bamfile}->{$sample}; my $tmp = $bam; $tmp =~ s/bam$/tmp/; - my $command = sprintf q{bash -c 'set -o pipefail ; zcat %s | samtools view -uT %s - | samtools sort -l 0 -T %s - | samtools calmd - %s > %s'}, + my $command = sprintf q{bash -c 'set -o pipefail ; zcat %s | pee "grep ^@" "grep -v ^@ | sort | uniq" | samtools view -uT %s - | samtools sort -l 0 -T %s - | samtools calmd - %s > %s'}, $sam, # zcat $self->{ref}, # view $tmp, # sort diff --git a/perl/t/vcfBlatAugment.t b/perl/t/vcfBlatAugment.t index 1678d96..1ca3fc5 100644 --- a/perl/t/vcfBlatAugment.t +++ b/perl/t/vcfBlatAugment.t @@ -16,11 +16,11 @@ const my @HEADER_ENDS => do { const my $HEADER_LINES => 3393; const my $DATA_ARR_D => [qw(chr10 11201 id CA C 390 . PC=D;RS=11201;RE=11205;LEN=1;S1=11;S2=849.236;REP=3 GT:PP:NP ./.:10:0)]; -const my $RES_ARR_D => [qw(chr10 11201 id CA C 390 . PC=D;RS=11201;RE=11205;LEN=1;S1=11;S2=849.236;REP=3 GT:PP:NP ./.:10:0:12:3:0.015:8:3:0.006:0.423)]; +const my $RES_ARR_D => [qw(chr10 11201 id CA C 390 . PC=D;RS=11201;RE=11205;LEN=1;S1=11;S2=849.236;REP=3 GT:PP:NP ./.:10:0:2:0:0.010:0:1:0.007:0.333)]; const my $DATA_ARR_DI => [qw(chr10 22777 id AGAAACTGTG ACTGTGAGATAGATATATATAGATAGATATAT 105 . PC=DI;RS=22777;RE=22787;LEN=9;S1=6;REP=0 GT:PP:NP ./.:0:5)]; -const my $RES_ARR_DI => [qw(chr10 22777 id AGAAACTGTG ACTGTGAGATAGATATATATAGATAGATATAT 105 . PC=DI;RS=22777;RE=22787;LEN=9;S1=6;REP=0 GT:PP:NP ./.:0:5:2:2:0.002:2:8:0.017:0.714)]; +const my $RES_ARR_DI => [qw(chr10 22777 id AGAAACTGTG ACTGTGAGATAGATATATATAGATAGATATAT 105 . PC=DI;RS=22777;RE=22787;LEN=9;S1=6;REP=0 GT:PP:NP ./.:0:5:1:2:0.002:0:1:0.007:0.250)]; const my $DATA_ARR_SI => [qw(chr10 11643 id C CG 150 . PC=I;RS=11643;RE=11649;LEN=1;S1=6;S2=421.908;REP=4 GT:PP:NP ./.:0:5)]; -const my $RES_ARR_SI => [qw(chr10 11643 id C CG 150 . PC=I;RS=11643;RE=11649;LEN=1;S1=6;S2=421.908;REP=4 GT:PP:NP ./.:0:5:7:10:0.014:7:10:0.005:0.500)]; +const my $RES_ARR_SI => [qw(chr10 11643 id C CG 150 . PC=I;RS=11643;RE=11649;LEN=1;S1=6;S2=421.908;REP=4 GT:PP:NP ./.:0:5:2:6:0.014:5:5:0.003:0.556)]; my ($stdout_fh, $buffer); my ($sam_stdout_fh, $sam_buffer); From bd547b8740d1eb3c13dc19508a0f0e0d624945e6 Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Tue, 16 Nov 2021 16:33:31 +0000 Subject: [PATCH 46/60] Improves large del processing --- .../Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm index 134aaeb..68fad7f 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm @@ -53,7 +53,7 @@ use Sanger::CGP::Vcf::VcfUtil; const my $SD_MULT => 2; const my $V_FMT => 8; const my $V_GT_START => 9; -const my $READS_ONLY => q{bash -c 'set -o pipefail; samtools view -uF 3840 %s %s | samtools fasta - > %s'}; +const my $READS_ONLY => q{bash -c 'set -o pipefail; (samtools view -H %s && samtools view -F 3840 %s %s | sort | uniq) | samtools fasta - > %s'}; const my $BLAT_ONLY => q{bash -c 'blat -t=dna -q=dna -noTrimA -minIdentity=95 -noHead -out=psl %s %s %s && pslPretty -long -axt %s %s %s /dev/stdout'}; # returns +ve and then -ve results const my $LOCI_FMT => '%s:%d-%d'; @@ -202,7 +202,7 @@ sub _buffer_sizes { } $self->{min_rl} = $min_rl; $self->{max_insert} = $max_ins; - #$self->{target_pad} = $max_rl; + $self->{max_rl} = $max_rl; $self->{target_pad} = $max_ins; # as expanded search space we need to expan the match space return 1; } @@ -361,6 +361,12 @@ sub read_ranges { my ($self, $v_h, $sample) = @_; # return a string of chr:s-e... if approprate. my $read_buffer = $self->{max_insert}; + # but need to handle very large deletions + if($v_h->{q_end} - $v_h->{q_start} > $read_buffer) { + my $low = sprintf $LOCI_FMT, $v_h->{CHROM}, $v_h->{q_start} - $read_buffer, $v_h->{q_start} + $read_buffer; + my $high = sprintf $LOCI_FMT, $v_h->{CHROM}, $v_h->{q_end} - $read_buffer, $v_h->{q_end} + $read_buffer; + return "$low $high"; + } return sprintf $LOCI_FMT, $v_h->{CHROM}, $v_h->{q_start} - $read_buffer, $v_h->{q_end} + $read_buffer; } @@ -371,7 +377,7 @@ sub blat_reads { my ($fh_psl, $file_psl) = tempfile( SUFFIX => '.psl', UNLINK => 1); close $fh_psl or die "Failed to close $file_psl (psl output)"; - my $c_reads = sprintf $READS_ONLY, $self->{hts}->{$sample}->hts_path, $self->read_ranges($v_h, $sample), $file_query; + my $c_reads = sprintf $READS_ONLY, $self->{hts}->{$sample}->hts_path, $self->{hts}->{$sample}->hts_path, $self->read_ranges($v_h, $sample), $file_query; if(! -e $file_query) { my ($r_out, $r_err, $r_exit) = capture { system([0], $c_reads); }; } From 1a10d7b2680438b6ab0584ce71fd5ab8f8bbc75f Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Mon, 13 Dec 2021 15:38:16 +0000 Subject: [PATCH 47/60] Addition of simple repeat filters --- perl/bin/pindelCohortVafFill.pl | 5 +- perl/bin/pindelCohortVafSliceFill.pl | 4 + perl/lib/Sanger/CGP/Pindel/Implement.pm | 3 + .../CGP/Pindel/OutputGen/VcfBlatAugment.pm | 125 ++++++++++++++---- 4 files changed, 110 insertions(+), 27 deletions(-) diff --git a/perl/bin/pindelCohortVafFill.pl b/perl/bin/pindelCohortVafFill.pl index 877740c..d71a6c8 100755 --- a/perl/bin/pindelCohortVafFill.pl +++ b/perl/bin/pindelCohortVafFill.pl @@ -120,7 +120,8 @@ sub setup { 'l|limit:i' => \$opts{limit}, 'a|abort' => \$opts{abort}, 'd|data=s' => \$opts{data}, - 'debug' => \$opts{debug} + 'debug' => \$opts{debug}, + 'sr|simple:s' => \$opts{simple}, ); if(defined $opts{v}) { @@ -153,6 +154,7 @@ sub setup { PCAP::Cli::file_for_reading('input', $opts{input}); PCAP::Cli::file_for_reading('data', $opts{data}); PCAP::Cli::file_for_reading('ref', $opts{ref}); + PCAP::Cli::file_for_reading('simple', $opts{simple}) if(defined $opts{simple}); if(@ARGV) { die "ERROR: No positional arguments expected." @@ -220,6 +222,7 @@ =head1 SYNOPSIS - Automatically increased to a minimum of 5x number of samples (for efficiency). -abort -a Abort noisily if data appears to have been processed (silent exit otherwise) -cpus -c Number of cores to use. [1] + -simple -sr Simple repeats file - only applies when complete search space within a single repeat. Targeted processing (further detail under OPTIONS): diff --git a/perl/bin/pindelCohortVafSliceFill.pl b/perl/bin/pindelCohortVafSliceFill.pl index 4dcd306..33be952 100755 --- a/perl/bin/pindelCohortVafSliceFill.pl +++ b/perl/bin/pindelCohortVafSliceFill.pl @@ -54,6 +54,7 @@ hts_files => $options->{hts_files}, outpath => $options->{outpath}, fill_in => 1, + simple_rpt => $options->{simple}, ); $augment->output_header; @@ -71,6 +72,7 @@ sub setup { 'r|ref=s' => \$opts{ref}, 'o|output=s' => \$opts{output}, 'd|data=s' => \$opts{data}, + 's|simple:s' => \$opts{simple}, ); if(defined $opts{v}) { @@ -82,6 +84,7 @@ sub setup { PCAP::Cli::file_for_reading('input', $opts{input}); PCAP::Cli::file_for_reading('ref', $opts{ref}); + PCAP::Cli::file_for_reading('somple', $opts{simple}) if(defined $opts{simple}); $opts{align} = $opts{output}.'.fill.sam' unless(defined $opts{align}); @@ -121,6 +124,7 @@ =head1 SYNOPSIS -output -o Directory for VCF output (gz compressed) and collocated sample bams -data -d File containing list of BWA mapping files for all samples used in "-input" - format: one BWA bam/cram file per line, expects co-located *.bai + -simple -s Simple repeats file - only applies when complete search space within a single repeat. Other: -help -h Brief help message. diff --git a/perl/lib/Sanger/CGP/Pindel/Implement.pm b/perl/lib/Sanger/CGP/Pindel/Implement.pm index be65c0b..6ee3194 100644 --- a/perl/lib/Sanger/CGP/Pindel/Implement.pm +++ b/perl/lib/Sanger/CGP/Pindel/Implement.pm @@ -754,6 +754,9 @@ sub fill_split_vaf { my $fill_dir = catdir($options->{fill_dir}, $fill_basename); my $command = $^X.' '._which('pindelCohortVafSliceFill.pl'); $command .= sprintf ' -r %s -i %s -o %s -d %s', $options->{ref}, $split_file, $fill_dir, $options->{bwa_file_list}; + if(defined $options->{'simple'}) { + $command .= sprintf ' -sr %s', $options->{'simple'}; + } PCAP::Threaded::external_process_handler(catdir($tmp, 'logs'), $command, $index); PCAP::Threaded::touch_success(catdir($tmp, 'progress'), $index); diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm index 68fad7f..24c2268 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm @@ -39,6 +39,8 @@ use File::Spec::Functions; use File::Temp qw(tempfile tempdir); use IO::Compress::Gzip qw(:constants gzip $GzipError); use List::Util qw(min max); +use Set::IntervalTree; +use IO::Uncompress::Gunzip qw(gunzip $GunzipError); use Bio::DB::HTS; use Bio::DB::HTS::Faidx; @@ -51,6 +53,7 @@ use Sanger::CGP::Vcf::VcfProcessLog; use Sanger::CGP::Vcf::VcfUtil; const my $SD_MULT => 2; +const my $V_INFO => 7; const my $V_FMT => 8; const my $V_GT_START => 9; const my $READS_ONLY => q{bash -c 'set -o pipefail; (samtools view -H %s && samtools view -F 3840 %s %s | sort | uniq) | samtools fasta - > %s'}; @@ -75,6 +78,7 @@ sub new{ hts_files => $args{hts_files}, fill_in => $args{fill_in}, debug => $args{debug} || 0, + rpts => $args{simple_rpt}, }; bless $self, $class; @@ -92,13 +96,75 @@ sub _init { $self->_align_output($outpath); $self->_add_headers unless($self->{fill_in}); $self->_hts; # has to go before _buffer_sizes - $self->_buffer_sizes; # has to go before _validate_sample + $self->_buffer_sizes; # has to go before _validate_sample and _pop_interval_tree $self->_validate_samples; + if($self->{fill_in}) { + $self->_pop_interval_tree; # adds a header item to VCF + } # load the fai $self->{fai} = Bio::DB::HTS::Faidx->new($self->{ref}); return 1; } +sub _pop_interval_tree { + my $self = shift; + # even if we don't have a file we should add the header for consistency + $self->{vcf}->add_header_line({key => 'INFO', ID => 'PSRPT', Description => q{BLAT search space is >50% simple repeat}}, 'append' => 1); + + my %tree; + if(defined $self->{rpts}) { + my $z = IO::Uncompress::Gunzip->new($self->{rpts}, MultiStream => 1) or die "gunzip failed: $GunzipError\n"; + while(my $line = <$z>) { + next if ($line =~ m/^#/); + chomp $line; + my ($chr, $s, $e) = split /\t/, $line; + my $dist = ($e - $s) + 1; + next if($dist < $self->{target_pad} * 2); + $tree{$chr} = Set::IntervalTree->new() unless(exists $tree{$chr}); + $tree{$chr}->insert([$s, $e, ($e - $s) + 1], $s, $e); + } + close $z; + } + $self->{repeat_tree} = \%tree; +} + +sub _padded_interval_hit { + my ($self, $chr, $l_pos, $h_pos) = @_; + return 0 unless (exists $self->{repeat_tree}->{$chr}); + my $l_pad = ($l_pos - $self->{target_pad}) - 1; # as completely 0-based + $l_pad = 0 if($l_pad < 0); + my $h_pad = $h_pos + $self->{target_pad}; + #warn sprintf "%s:%d-%d\n", $chr, $l_pad, $h_pad; + for my $hit(@{$self->{repeat_tree}->{$chr}->fetch($l_pad, $h_pad)}) { + #warn sprintf "\t%s:%d-%d (%d)\n", $chr, $hit->[0], $hit->[1], $hit->[2]; + my ($numer, $denom); + if($l_pad > $hit->[0] && $h_pad < $hit->[1]) { + # contained, as completely repeat + $numer = 1; + $denom = 1; # as completely repeat + } + elsif($l_pad <= $hit->[0]) { + # low overhang + $numer = ($h_pad - $hit->[0]) + 1; + $denom = ($h_pad - $l_pad) + 1 + } + elsif($h_pad >= $hit->[1]) { + # high overhang + $numer = ($hit->[1] - $l_pad) + 1; + $denom = ($h_pad - $l_pad) + 1 + } + else { + # search is larger than the repeat, but could still be huge component + $numer = $hit->[2]; + $denom = ($h_pad - $l_pad) + 1; + } + my $result = $numer/$denom; + #warn "\t\t$result\n"; + return 1 if($result > 0.5); + } + return 0; +} + sub _close_sams { my $self = shift; for my $sample(@{$self->{vcf_sample_order}}) { @@ -326,34 +392,41 @@ sub blat_record { my ($self, $v_d, $readtmp_dir) = @_; my $v_h = $self->to_data_hash($v_d); - # now attempt the blat stuff - my ($fh_target, $file_target) = tempfile( SUFFIX => '.fa', UNLINK => 1 ); - $self->blat_ref_alt($fh_target, $v_h); - close $fh_target or die "Failed to close blat ref temp file"; - - my $change_pos_low = $v_h->{change_pos}; - $change_pos_low++ if($v_h->{PC} eq 'I'); - my $range_l = ($v_h->{RE} - $v_h->{RS}) + 1; - my $change_pos_high = $change_pos_low + $range_l; # REF based range, adjusted in func - $v_h->{change_pos_low} = $change_pos_low; - $v_h->{change_pos_high} = $change_pos_high; + # assess if this is completely embedded in a large simple repeat + my $simp_rep = $self->_padded_interval_hit($v_h->{CHROM}, $v_h->{RS}, $v_h->{RE}); - my $gt_pos = $V_GT_START-1; - for my $sample(@{$self->{vcf_sample_order}}) { - $gt_pos++; - if($self->{fill_in} && $v_d->[$gt_pos] ne q{.}) { - next; - } - my $gt_set = $self->blat_reads($v_h, $file_target, $readtmp_dir, $sample); - if($v_d->[$gt_pos] eq q{.}) { - $v_d->[$gt_pos] = join q{:}, './.:.:.:.:.', @{$gt_set}; - } - else { - $v_d->[$gt_pos] = join q{:}, $v_d->[$gt_pos], @{$gt_set}; + if($simp_rep == 1) { + $v_d->[$V_INFO] .= ';PSRPT' + } + else { + # now attempt the blat stuff + my ($fh_target, $file_target) = tempfile( SUFFIX => '.fa', UNLINK => 1 ); + $self->blat_ref_alt($fh_target, $v_h); + close $fh_target or die "Failed to close blat ref temp file"; + + my $change_pos_low = $v_h->{change_pos}; + $change_pos_low++ if($v_h->{PC} eq 'I'); + my $range_l = ($v_h->{RE} - $v_h->{RS}) + 1; + my $change_pos_high = $change_pos_low + $range_l; # REF based range, adjusted in func + $v_h->{change_pos_low} = $change_pos_low; + $v_h->{change_pos_high} = $change_pos_high; + my $gt_pos = $V_GT_START-1; + for my $sample(@{$self->{vcf_sample_order}}) { + $gt_pos++; + if($self->{fill_in} && $v_d->[$gt_pos] ne q{.}) { + next; + } + my $gt_set = $self->blat_reads($v_h, $file_target, $readtmp_dir, $sample); + if($v_d->[$gt_pos] eq q{.}) { + $v_d->[$gt_pos] = join q{:}, './.:.:.:.:.', @{$gt_set}; + } + else { + $v_d->[$gt_pos] = join q{:}, $v_d->[$gt_pos], @{$gt_set}; + } } + # tempfile unlink only does it on shutdown when used in this way + unlink $file_target; } - # tempfile unlink only does it on shutdown when used in this way - unlink $file_target; return 1; } From 83af748e445736684f8b3766bdfea9c4f1fda44d Mon Sep 17 00:00:00 2001 From: Keiran Raine Date: Thu, 20 Jan 2022 16:11:41 +0000 Subject: [PATCH 48/60] some docs --- .../CGP/Pindel/OutputGen/VcfBlatAugment.pm | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm index 24c2268..138022d 100644 --- a/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm +++ b/perl/lib/Sanger/CGP/Pindel/OutputGen/VcfBlatAugment.pm @@ -66,6 +66,24 @@ const my $MAPPED_RL_MULT => 0.9; 1; +=head new + +Basic code template for use: + + $augment = Sanger::CGP::Pindel::OutputGen::VcfBlatAugment->new( + input => $options->{input}, + ref => $options->{ref}, + ofh => $options->{output}, + sam => $options->{align}, + hts_files => $options->{hts}, + outpath => $options->{outpath}, + debug => $options->{debug}, + ); + $augment->output_header; + $augment->process_records; + +=cut + sub new{ my $proto = shift; my (%args) = @_; @@ -96,17 +114,17 @@ sub _init { $self->_align_output($outpath); $self->_add_headers unless($self->{fill_in}); $self->_hts; # has to go before _buffer_sizes - $self->_buffer_sizes; # has to go before _validate_sample and _pop_interval_tree + $self->_buffer_sizes; # has to go before _validate_sample and _populate_interval_tree $self->_validate_samples; if($self->{fill_in}) { - $self->_pop_interval_tree; # adds a header item to VCF + $self->_populate_interval_tree; # adds a header item to VCF } # load the fai $self->{fai} = Bio::DB::HTS::Faidx->new($self->{ref}); return 1; } -sub _pop_interval_tree { +sub _populate_interval_tree { my $self = shift; # even if we don't have a file we should add the header for consistency $self->{vcf}->add_header_line({key => 'INFO', ID => 'PSRPT', Description => q{BLAT search space is >50% simple repeat}}, 'append' => 1); @@ -366,6 +384,12 @@ sub to_data_hash { return \%out; } +=head process_records + +Primary entry point for action following new() + +=cut + sub process_records { my $self = shift; my $fh = $self->{ofh}; @@ -380,6 +404,7 @@ sub process_records { } $v_d->[$V_FMT] .= $self->{fmt_ext} unless($self->{fill_in}); $self->blat_record($v_d, $readtmp_dir); + # output the updated record printf $fh "%s\n", join "\t", @{$v_d}; } if(-d $readtmp_dir) { From bf3e985e1b245431eee44897cc072b2abe9f7583 Mon Sep 17 00:00:00 2001 From: Raul Alcantara Date: Fri, 30 Sep 2022 20:13:36 +0100 Subject: [PATCH 49/60] Update Implement.pm added pipefail option for pindel commands --- perl/lib/Sanger/CGP/Pindel/Implement.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/perl/lib/Sanger/CGP/Pindel/Implement.pm b/perl/lib/Sanger/CGP/Pindel/Implement.pm index 6ee3194..5450b72 100644 --- a/perl/lib/Sanger/CGP/Pindel/Implement.pm +++ b/perl/lib/Sanger/CGP/Pindel/Implement.pm @@ -143,6 +143,7 @@ sub pindel { my @command_set; + push @command_set, "set -o pipefail"; # was split my $refs = catdir($tmp, 'refs'); make_path($refs) unless(-e $refs); From 03a97ddbe0e03600ef87f7ed86498e4cbc16c4b9 Mon Sep 17 00:00:00 2001 From: Raul Alcantara Date: Mon, 10 Oct 2022 21:51:42 +0100 Subject: [PATCH 50/60] Update pindelCohortVafSliceFill.pl Fixed simple repeat input parameter --- perl/bin/pindelCohortVafSliceFill.pl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/perl/bin/pindelCohortVafSliceFill.pl b/perl/bin/pindelCohortVafSliceFill.pl index 33be952..44bdf82 100755 --- a/perl/bin/pindelCohortVafSliceFill.pl +++ b/perl/bin/pindelCohortVafSliceFill.pl @@ -72,7 +72,7 @@ sub setup { 'r|ref=s' => \$opts{ref}, 'o|output=s' => \$opts{output}, 'd|data=s' => \$opts{data}, - 's|simple:s' => \$opts{simple}, + 'sr|simple:s' => \$opts{simple}, ); if(defined $opts{v}) { @@ -124,7 +124,7 @@ =head1 SYNOPSIS -output -o Directory for VCF output (gz compressed) and collocated sample bams -data -d File containing list of BWA mapping files for all samples used in "-input" - format: one BWA bam/cram file per line, expects co-located *.bai - -simple -s Simple repeats file - only applies when complete search space within a single repeat. + -simple -sr Simple repeats file - only applies when complete search space within a single repeat. Other: -help -h Brief help message. From cb472f9695bc4e2b01b9c35179c109f979201d48 Mon Sep 17 00:00:00 2001 From: Raul Alcantara Date: Mon, 10 Oct 2022 21:53:10 +0100 Subject: [PATCH 51/60] Update pindelCohortVafSliceFill.pl corrected spelling error --- perl/bin/pindelCohortVafSliceFill.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/perl/bin/pindelCohortVafSliceFill.pl b/perl/bin/pindelCohortVafSliceFill.pl index 44bdf82..70d8a52 100755 --- a/perl/bin/pindelCohortVafSliceFill.pl +++ b/perl/bin/pindelCohortVafSliceFill.pl @@ -84,7 +84,7 @@ sub setup { PCAP::Cli::file_for_reading('input', $opts{input}); PCAP::Cli::file_for_reading('ref', $opts{ref}); - PCAP::Cli::file_for_reading('somple', $opts{simple}) if(defined $opts{simple}); + PCAP::Cli::file_for_reading('simple', $opts{simple}) if(defined $opts{simple}); $opts{align} = $opts{output}.'.fill.sam' unless(defined $opts{align}); From 8f53e43939cebadd1823f06da2aa5d117061659c Mon Sep 17 00:00:00 2001 From: Raul Alcantara Date: Tue, 29 Nov 2022 16:22:32 +0000 Subject: [PATCH 52/60] metanorm filtering rules --- .../MetanormFilterRules.pm | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 perl/lib/Sanger/CGP/PindelPostProcessing/MetanormFilterRules.pm diff --git a/perl/lib/Sanger/CGP/PindelPostProcessing/MetanormFilterRules.pm b/perl/lib/Sanger/CGP/PindelPostProcessing/MetanormFilterRules.pm new file mode 100644 index 0000000..59b70fd --- /dev/null +++ b/perl/lib/Sanger/CGP/PindelPostProcessing/MetanormFilterRules.pm @@ -0,0 +1,123 @@ +# Copyright (c) 2014-2021 Genome Research Ltd +# +# Author: CASM/Cancer IT +# +# This file is part of cgpPindel. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# 1. The usage of a range of years within a copyright statement contained within +# this distribution should be interpreted as being equivalent to a list of years +# including the first and last year specified and all consecutive years between +# them. For example, a copyright statement that reads ‘Copyright (c) 2005, 2007- +# 2009, 2011-2012’ should be interpreted as being identical to a statement that +# reads ‘Copyright (c) 2005, 2007, 2008, 2009, 2011, 2012’ and a copyright +# statement that reads ‘Copyright (c) 2005-2012’ should be interpreted as being +# identical to a statement that reads ‘Copyright (c) 2005, 2006, 2007, 2008, +# 2009, 2010, 2011, 2012’. +# +package Sanger::CGP::PindelPostProcessing::MetanormFilterRules; + +use strict; +use Bio::DB::HTS::Tabix; +use Sanger::CGP::Pindel; + +my %RULE_DESCS = ( + 'F006' => { 'tag' => 'INFO/LEN', + 'name' => 'F006', + 'desc' => 'Small call excessive repeat check: Fail if Length <= 4 and Repeats > 9', + 'test' => \&flag_006}, + 'F017' => { 'tag' => 'INFO/LEN', + 'name' => 'F017', + 'desc' => 'Variant must not overlap with a simple repeat', + 'test' => \&flag_017}, + 'LONG' => { 'tag' => 'INFO/LEN', + 'name' => 'LONG', + 'desc' => 'Event larger than 1kbp', + 'test' => \&long} + +); + +our $previous_format_hash; +our $previous_format_string = q{}; +our $vcf_flagging_repeats_tabix; + +sub rule { + my (undef, $rule) = @_; # being called like an object function so throw away first varaible + return $RULE_DESCS{$rule}; +} + +sub available_rules { + return sort keys %RULE_DESCS; +} + +sub use_prev { + my $format = shift; + ### HACK Dirty dirty dirty...... done to try and cut down the number of times I have to parse the FORMAT string I am storing it as a global variable. + if($format ne $previous_format_string){ + my $i = 0; + map {$previous_format_hash->{$_} = $i++} split(':',$format); + $previous_format_string = $format; + } +} + +sub reuse_repeats_tabix { + unless(defined $vcf_flagging_repeats_tabix) { + $vcf_flagging_repeats_tabix = new Bio::DB::HTS::Tabix(filename=> $ENV{VCF_FLAGGING_REPEATS}); + } +} + +sub flag_006 { + my ($MATCH,$CHROM,$POS,$FAIL,$PASS,$RECORD,$VCF) = @_; + if($MATCH <= 4){ + my ($rep) = $$RECORD[7] =~ /REP=(\d+)/; + if($rep > 9) { + return $FAIL; + } + } + return $PASS; +} + +sub flag_017 { + my ($MATCH,$CHROM,$POS,$FAIL,$PASS,$RECORD,$VCF) = @_; + reuse_repeats_tabix(); + + my($from) = ";$$RECORD[7]" =~ m/;RS=(\d+)/; + my($to) = ";$$RECORD[7]" =~ m/;RE=(\d+)/; + + my $ret = eval{ + # as vcf POS for indels is the previous base pos is 0-based, but the new TABIX requires 1-based + my $iter = $vcf_flagging_repeats_tabix->query_full($CHROM,$from,$to); + return $PASS if(!defined $iter); # no valid entries (chromosome not in index) so must pass + while($iter->next){ + return $FAIL; + } + return $PASS; + }; + if($@) { + die $@; + } + return $ret; +} + +sub long { + my ($MATCH,$CHROM,$POS,$FAIL,$PASS,$RECORD,$VCF) = @_; + if($MATCH > 1000 ){ + return $FAIL + } + return $PASS; +} + + +1; From 9113d8a10f0a78f8aeb559fda5cd99a38dd46f21 Mon Sep 17 00:00:00 2001 From: Raul Alcantara Date: Tue, 29 Nov 2022 17:12:42 +0000 Subject: [PATCH 53/60] Create metanormRules.lst --- perl/rules/metanormRules.lst | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 perl/rules/metanormRules.lst diff --git a/perl/rules/metanormRules.lst b/perl/rules/metanormRules.lst new file mode 100644 index 0000000..5f61282 --- /dev/null +++ b/perl/rules/metanormRules.lst @@ -0,0 +1,4 @@ +Sanger::CGP::PindelPostProcessing::MetanormFilterRules +F006 +F017 +LONG From ac057fb546d115ade4f2b4912a3a88fc60c3c080 Mon Sep 17 00:00:00 2001 From: Raul Alcantara Date: Wed, 30 Nov 2022 11:19:13 +0000 Subject: [PATCH 54/60] Update Pindel.pm Added version for cohort code --- perl/lib/Sanger/CGP/Pindel.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/perl/lib/Sanger/CGP/Pindel.pm b/perl/lib/Sanger/CGP/Pindel.pm index af28e50..94dd691 100644 --- a/perl/lib/Sanger/CGP/Pindel.pm +++ b/perl/lib/Sanger/CGP/Pindel.pm @@ -34,6 +34,7 @@ use Const::Fast qw(const); use base 'Exporter'; our $VERSION = '3.6.0'; -our @EXPORT = qw($VERSION); +our $COHORT_VERSION = '1.0.0'; +our @EXPORT = qw($VERSION $COHORT_VERSION); 1; From cb8479aaff2ed3aa74961221f292db24e0ec34b0 Mon Sep 17 00:00:00 2001 From: Raul Alcantara Date: Wed, 30 Nov 2022 11:22:14 +0000 Subject: [PATCH 55/60] Update Implement.pm Added version number for cpindel --- perl/lib/Sanger/CGP/Pindel/Implement.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/perl/lib/Sanger/CGP/Pindel/Implement.pm b/perl/lib/Sanger/CGP/Pindel/Implement.pm index 5450b72..c50e968 100644 --- a/perl/lib/Sanger/CGP/Pindel/Implement.pm +++ b/perl/lib/Sanger/CGP/Pindel/Implement.pm @@ -639,7 +639,8 @@ sub shared_setup { pod2usage(-verbose => 2) if(defined $opts{'m'}); if($opts{'version'}) { - print 'Version: ',Sanger::CGP::Pindel::Implement->VERSION,"\n"; + print 'pindel version: ',Sanger::CGP::Pindel::Implement->VERSION,"\n"; + print 'cpindel version: ', Sanger::CGP::Pindel::Implement->COHORT_VERSION,"\n"; exit 0; } From b1be25f76d66de0cd8a331aa9b71cd9538bd79e3 Mon Sep 17 00:00:00 2001 From: Raul Alcantara Date: Wed, 30 Nov 2022 11:56:14 +0000 Subject: [PATCH 56/60] Update Implement.pm --- perl/lib/Sanger/CGP/Pindel/Implement.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/perl/lib/Sanger/CGP/Pindel/Implement.pm b/perl/lib/Sanger/CGP/Pindel/Implement.pm index c50e968..51a26bc 100644 --- a/perl/lib/Sanger/CGP/Pindel/Implement.pm +++ b/perl/lib/Sanger/CGP/Pindel/Implement.pm @@ -640,7 +640,7 @@ sub shared_setup { if($opts{'version'}) { print 'pindel version: ',Sanger::CGP::Pindel::Implement->VERSION,"\n"; - print 'cpindel version: ', Sanger::CGP::Pindel::Implement->COHORT_VERSION,"\n"; + print 'cpindel version: ',$COHORT_VERSION,"\n"; exit 0; } From 4eab4bd6b0dbf3634eddead72e72f12f8c4e54e7 Mon Sep 17 00:00:00 2001 From: Raul Alcantara Date: Wed, 30 Nov 2022 14:20:56 +0000 Subject: [PATCH 57/60] Update CHANGES.md Removed all pindel stuff, made realease 1.0.0 of cpindel --- CHANGES.md | 194 ++--------------------------------------------------- 1 file changed, 4 insertions(+), 190 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 1d1a084..d97b605 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,8 @@ # CHANGES -## NEXT - +## 1.0.0 +- Added filters to FlagVcf.pl to allow flagging of per-sample vcf outputs +- Fixed bugs in Implement.pm and pindelCohortVafSliceFill.pl - Adds code to allow single sample processing with more accurate VAF calculations (via BLAT) - Status of new scripts, "pre-release" indicates defaults and CLI may change: - stable @@ -14,192 +15,5 @@ - pindelCohortVafFill.pl - pindelCohortVafSplit.pl - pindelCohortVafSliceFill.pl +- pinning to pindel v3.6.0 - Switch license management to skywalking-eyes. - -## 3.6.0 - -- Addition of `FF019` and `FF020` flags -- New flag rule set `pulldownFfpeRulesFragment.lst` including FF019 and FF020 made - -## 3.5.0 - -- Update to core pindel algorithm to allow complex DI events to have longer inserted sequence than deleted - - Masking real events - -## 3.4.1 - -- Updated Dockerfile to use pcap-core 5.4.0 - htslib/samtools 1.11 - -## 3.4.0 - -- Updated Dockerfile to use pcap-core 5.2.2 -- Modified setup script to use build/\*.sh - -## 3.3.0 - -- I/O hardening, see [milestone 3](https://github.com/cancerit/cgpPindel/milestone/3) - -## 3.2.2 - -- Handle Input files that may have no reads at all, specifically an issue when generating a normal panel. - -## 3.2.1 - -- Added Dockerfile and docker documentation - -## 3.2.0 - -- Tabix search for high depth/excluded regions now performed in memory using IntervalTrees - - Reduces runtime of input step by ~50% - - Improved disk access profile - - Zero impact on results - -## 3.1.2 - -- 3.0.5 introduced species parsing bug causing single word species names to be invalid. - -## 3.1.1 - -- Fix regression - ability to cope with chromosomes with no events. - -## 3.1.0 - -- Incorporates updated pindel which improves sensitivity -- Internally interpret QCFAIL to determine if whole pair fails - -## 3.0.6 - -- Fixed version tag - -## 3.0.5 - -- Handles species names with spaces in it -- modified checks for species,assembly and checksum - -## 3.0.4 - -- Output bug for pindel BAM/CRAM corrected. When more than 1 chr in output files had no reads. - -## 3.0.3 - -- Changes to how germline filter determined resulted in dummy germline bed file not being generated as previously. -- This release reinstates the old behaviour. - -## 3.0.2 - -- Correct example rule files for \*Fragment.lst files to use FFnnn filter types - -## 3.0.1 - -- Update tabix calls to directly use query_full (solves GRCh38 contig name issues). - -## 3.0.0 - -- Germline bed file is now merged for adjacent regions (#31) -- More compressed intermediate files (#55) -- Change to `Const::Fast` where appropriate (#41) -- Removed TG VG from genotype. - - Readgroups are always variable, often 1 in data from last few years - - Not used by our filters. -- Supports BAM/CRAM inputs -- Output will be aligned with inputs - - bam vs cram - - bai vs csi -- Although ground work for csi input/output has been done `Bio::DB::HTS` doesn't support csi indexed input yet. - - Created our own fork at [`cancerit/Bio::DB::HTS`][cancerit-biodbhts] so that this could be enabled. - - You will need to install this manually or use one of our images for this functionallity. - - [dockstore-cgpwxs][ds-cgpwxs-git] - - [dockstore-cgpwxs][ds-cgpwgs-git] - - - -## 2.2.5 - -- Update tabix->query to tabix->query_full - -## 2.2.4 - -- Force sorting of FILTER field to make records easier to diff. -- Fix sorting of final VCF to handle events with same start better when using comparison tools - -## 2.2.3 - -Correct read sorting during collection of DI events. Caused some events to be split into many and -others to be missed (Thanks to @liangkaiye for patch) - -## 2.2.3 - -Correct read sorting during collection of DI events. Caused some events to be split into many and -others to be missed (Thanks to @liangkaiye for patch) - -## 2.2.2 - -Correction to sorting of VCF files - -## 2.2.0 - -Reduces the amount of temporary space required and overall I/O - -To process 40 million readpairs (40x Tumour + 40x Normal, chr21, 100bp reads): - -Original time: - -``` -User time (seconds): 3553.88 -System time (seconds): 63.92 -Percent of CPU this job got: 159% -Elapsed (wall clock) time (h:mm:ss or m:ss): 37:51.63 -File system inputs: 64 -File system outputs: 1782080 -``` - -New time: - -``` -User time (seconds): 3572.21 -System time (seconds): 74.06 -Percent of CPU this job got: 167% -Elapsed (wall clock) time (h:mm:ss or m:ss): 36:15.01 -File system inputs: 0 -File system outputs: 1139128 -``` - -``` -Original peak size: 650MB - New peak size: 291MB -``` - -__~55%__ reduction in working space and about __40%__ fewer writes to the file system. - -Exactly the same results: - -```bash -$ diff old/f9c3bc8e-dbc4-1ed0-e040-11ac0d4803a9_vs_f9c3bc8e-dbc1-1ed0-e040-11ac0d4803a9.germline.bed new/f9c3bc8e-dbc4-1ed0-e040-11ac0d4803a9_vs_f9c3bc8e-dbc1-1ed0-e040-11ac0d4803a9.germline.bed - -$ diff_bams -a old/f9c3bc8e-dbc4-1ed0-e040-11ac0d4803a9_vs_f9c3bc8e-dbc1-1ed0-e040-11ac0d4803a9_wt.bam -b new/f9c3bc8e-dbc4-1ed0-e040-11ac0d4803a9_vs_f9c3bc8e-dbc1-1ed0-e040-11ac0d4803a9_wt.bam -Reference sequence count passed -Reference sequence order passed -Matching records: 194543 - -$ diff_bams -a old/f9c3bc8e-dbc4-1ed0-e040-11ac0d4803a9_vs_f9c3bc8e-dbc1-1ed0-e040-11ac0d4803a9_mt.bam -b new/f9c3bc8e-dbc4-1ed0-e040-11ac0d4803a9_vs_f9c3bc8e-dbc1-1ed0-e040-11ac0d4803a9_mt.bam -Reference sequence count passed -Reference sequence order passed -Matching records: 239737 - -$ /software/CGP/canpipe/live/bin/canpipe_live vcftools --gzvcf old/f9c3bc8e-dbc4-1ed0-e040-11ac0d4803a9_vs_f9c3bc8e-dbc1-1ed0-e040-11ac0d4803a9.flagged.vcf.gz --gzdiff new/f9c3bc8e-dbc4-1ed0-e040-11ac0d4803a9_vs_f9c3bc8e-dbc1-1ed0-e040-11ac0d4803a9.flagged.vcf.gz -... -Comparing individuals in VCF files... -N_combined_individuals: 2 -N_individuals_common_to_both_files: 2 -N_individuals_unique_to_file1: 0 -N_individuals_unique_to_file2: 0 -Comparing sites in VCF files... -Found 15321 SNPs common to both files. -Found 0 SNPs only in main file. -Found 0 SNPs only in second file. -After -``` - -[cancerit-biodbhts]: https://github.com/cancerit/Bio-DB-HTS/releases/tag/v2.10-rc1 -[ds-cgpwgs-git]: https://github.com/cancerit/dockstore-cgpwgs -[ds-cgpwxs-git]: https://github.com/cancerit/dockstore-cgpwxs From 93839f590ad21f9accbc9c54fd098812bfef8278 Mon Sep 17 00:00:00 2001 From: Raul Alcantara Date: Fri, 23 Dec 2022 11:46:49 +0000 Subject: [PATCH 58/60] Update Implement.pm Added a test of the sam.gz file created by blat --- perl/lib/Sanger/CGP/Pindel/Implement.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/perl/lib/Sanger/CGP/Pindel/Implement.pm b/perl/lib/Sanger/CGP/Pindel/Implement.pm index 51a26bc..095499b 100644 --- a/perl/lib/Sanger/CGP/Pindel/Implement.pm +++ b/perl/lib/Sanger/CGP/Pindel/Implement.pm @@ -316,7 +316,7 @@ sub blat { $options->{hts_files}->[0], $split_file, catfile($tmp, $blat_file); - + $command .= sprintf("gzip -t %s/*.gz", catfile($tmp, $blat_file)); PCAP::Threaded::external_process_handler(catdir($tmp, 'logs'), $command, $index); PCAP::Threaded::touch_success(catdir($tmp, 'progress'), $index); } From 66dd39d17444377d72809c4764305c3c0545a3cc Mon Sep 17 00:00:00 2001 From: Raul Alcantara Date: Fri, 23 Dec 2022 16:06:55 +0000 Subject: [PATCH 59/60] upped version --- CHANGES.md | 3 +++ perl/lib/Sanger/CGP/Pindel.pm | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index d97b605..c4c9af7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,8 @@ # CHANGES +## 1.0.1 +- Added a file check after blat step + ## 1.0.0 - Added filters to FlagVcf.pl to allow flagging of per-sample vcf outputs - Fixed bugs in Implement.pm and pindelCohortVafSliceFill.pl diff --git a/perl/lib/Sanger/CGP/Pindel.pm b/perl/lib/Sanger/CGP/Pindel.pm index 94dd691..671e16b 100644 --- a/perl/lib/Sanger/CGP/Pindel.pm +++ b/perl/lib/Sanger/CGP/Pindel.pm @@ -34,7 +34,7 @@ use Const::Fast qw(const); use base 'Exporter'; our $VERSION = '3.6.0'; -our $COHORT_VERSION = '1.0.0'; +our $COHORT_VERSION = '1.0.1'; our @EXPORT = qw($VERSION $COHORT_VERSION); 1; From 011bd279423303945f8d0da4df1c40a88ae71e18 Mon Sep 17 00:00:00 2001 From: Raul Alcantara Date: Fri, 23 Dec 2022 16:31:49 +0000 Subject: [PATCH 60/60] reformatted blat command --- perl/lib/Sanger/CGP/Pindel/Implement.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/perl/lib/Sanger/CGP/Pindel/Implement.pm b/perl/lib/Sanger/CGP/Pindel/Implement.pm index 095499b..a0ca73a 100644 --- a/perl/lib/Sanger/CGP/Pindel/Implement.pm +++ b/perl/lib/Sanger/CGP/Pindel/Implement.pm @@ -311,12 +311,12 @@ sub blat { my $blat_file = fileparse($split_file, '.vcf'); $blat_file =~ s/split_([a-z]+)/blat_$1/; my $command = $^X.' '._which('pindel_blat_vaf.pl'); - $command .= sprintf q{ -r %s -hts %s -i %s -o %s}, + $command .= sprintf q{ -r %s -hts %s -i %s -o %s ; gzip -t %s/*.gz }, $options->{reference}, $options->{hts_files}->[0], $split_file, + catfile($tmp, $blat_file), catfile($tmp, $blat_file); - $command .= sprintf("gzip -t %s/*.gz", catfile($tmp, $blat_file)); PCAP::Threaded::external_process_handler(catdir($tmp, 'logs'), $command, $index); PCAP::Threaded::touch_success(catdir($tmp, 'progress'), $index); }