Mercurial > repos > jjohnson > crest
view StructVar.pm @ 0:acc8d8bfeb9a
Uploaded
author | jjohnson |
---|---|
date | Wed, 08 Feb 2012 16:59:24 -0500 |
parents | |
children |
line wrap: on
line source
package StructVar; # $Id: StructVar.pm, v 1.00 2010/09/24 jianmin.wang Exp $ use strict; use Bio::DB::Sam; use Carp; use Data::Dumper; use English; use Bio::SearchIO; use GeneModel; use Cwd; use SVExtTools; use List::MoreUtils qw/ uniq /; use SVUtil qw( prepare_reads read_fa_file get_sclip_reads rev_comp); use SCValidator qw(LEFT_CLIP RIGHT_CLIP); use constant UNK => 0; use constant CTX => 1; use constant INV => 2; use constant INS => 3; use constant DEL => 4; use constant ITX => 5; # type of SVs and thier string my %type2str = ( 0 => 'UNKNOWN', # unknow type, not single event 1 => 'CTX', # cross chromosome translocation 2 => 'INV', # inversion, requires both breakpoints available 3 => 'INS', # (tandem) insertion 4 => 'DEL', # deletion 5 => 'ITX', # within chromosome translocation ); # class static variables my $sam_d; my $sam_g; my $gm; my $assembler; my $aligner; my $mapper; my @filters = (\&_germline_sclip_filter, \&_type_distance_filter, \&_germline_indel_filter); my $RNASeq; my $read_len; my $genome; my $tr_max_indel_size; #tandem repeat mediated indel size my $tr_min_size; my $tr_max_size; #tandem repeat max_size of single repeat my $tr_min_num; #tandem repeat minimum number of occurence my $max_bp_dist = 15; my $germline_seq_width = 100; my $germline_search_width = 50; my $min_percent_cons_of_read = 0.75; my %default_filters = ( 'germline_sclip' => \&_germline_sclip_filter, 'type_distance' => \&_type_distance_filter, 'germline_indel' => \&_germline_indel_filter, # 'mapping_artifact' => \&_mapping_artifact_filter, 'mapping_quality' => \&_mapping_quality_filter, 'tandem_repeat' => \&_tandem_repeat_filter, ); sub new { my $class = shift; my %param = @_; my $self = {}; $self->{first_bp} = {}; $self->{second_bp} = {}; $self->{first_bp} = $param{-FIRST_BP} if($param{-FIRST_BP}); $self->{second_bp} = $param{-SECOND_BP} if($param{-SECOND_BP}); $self->{consensus} = $param{-CONSENSUS} ? $param{-CONSENSUS} : ""; $self->{type} = ""; bless $self, ref($class) || $class; return $self; } sub add_filter { my $self = shift; my $name = shift; my $f = shift; croak "you must specify a filter" if(!$f); croak "the filter must be a subroutine" if(ref($f) ne 'CODE'); $default_filters{$name} = $f; } sub add_RNASeq_filter { $default_filters{'RNASeq_strand'} = \&_RNASeq_strand_filter; $default_filters{'RNASeq_INS'} = \&_RNASeq_INS_filter; } sub remove_filter { my $self = shift; my $name = shift; if($default_filters{$name}){ delete $default_filters{$name}; } else { print STDERR "Filter $name is not in filter list"; } } sub tr_max_indel_size { #tandem repeat mediated indel size my $self = shift; my $value = shift; $tr_max_indel_size = $value if($value); return $tr_max_indel_size; } sub min_percent_cons_of_read { my $self= shift; my $value = shift; $min_percent_cons_of_read = $value if($value); return $min_percent_cons_of_read; } sub tr_min_size { my $self = shift; my $value = shift; $tr_min_size = $value if($value); return $tr_min_size; } sub germline_seq_width { my $self = shift; my $value = shift; $germline_seq_width = $value if($value); return $germline_seq_width; } sub germline_search_width { my $self = shift; my $value = shift; $germline_search_width = $value if($value); return $germline_search_width; } sub max_bp_dist { my $self = shift; my $value = shift; $max_bp_dist = $value if($value); return $max_bp_dist; } sub tr_max_size { #tandem repeat max_size of single repeat my $self = shift; my $value = shift; $tr_max_size = $value if($value); return $tr_max_size; } sub tr_min_num { #tandem repeat minimum number of occurence my $self = shift; my $value = shift; $tr_min_num = $value if($value); return $tr_min_num; } sub genome { my $self = shift; my $value = shift; $genome = $value if($value); return $genome; } sub sam_d { my $self = shift; my $value = shift; $sam_d = $value if($value); return $sam_d; } sub sam_g { my $self = shift; my $value = shift; $sam_g = $value if($value); return $sam_g; } sub assembler { my $self = shift; my $value = shift; $assembler = $value if($value); return $assembler; } sub aligner { my $self = shift; my $value = shift; $aligner = $value if($value); return $aligner; } sub RNASeq { my $self = shift; my $value = shift; $RNASeq = 1 if($value); return $RNASeq; } sub mapper { my $self = shift; my $value = shift; $mapper = $value if($value); return $mapper; } sub gene_model { my $self = shift; my $value = shift; $gm = $value if($value); return $gm; } sub read_len { my $self = shift; my $value = shift; $read_len = $value if($value); return $read_len; } sub first_bp { my $self = shift; my %param = @_; if(%param) { $self->{first_bp}{left_chr} = $param{-LEFT_CHR}; $self->{first_bp}{left_pos} = $param{-LEFT_POS}; $self->{first_bp}{left_ort} = $param{-LEFT_ORT}; $self->{first_bp}{right_chr} = $param{-RIGHT_CHR}; $self->{first_bp}{right_pos} = $param{-RIGHT_POS}; $self->{first_bp}{right_ort} = $param{-RIGHT_ORT}; $self->{first_bp}{sc_reads} = $param{-SC_READS}; $self->{first_bp}{pos} = $param{-POS}; $self->{first_bp}{cover} = $param{-COVER}; $self->{first_bp}{sc_seq} = $param{-SC_SEQ}; $self->{first_bp}{chr} = $param{-CHR}; } return $self->{first_bp}; } sub second_bp { my $self = shift; my %param = @_; if(%param) { $self->{second_bp}{left_chr} = $param{-LEFT_CHR}; $self->{second_bp}{left_pos} = $param{-LEFT_POS}; $self->{second_bp}{left_ort} = $param{-LEFT_ORT}; $self->{second_bp}{right_chr} = $param{-RIGHT_CHR}; $self->{second_bp}{right_pos} = $param{-RIGHT_POS}; $self->{second_bp}{right_ort} = $param{-RIGHT_ORT}; $self->{second_bp}{sc_reads} = $param{-SC_READS}; $self->{second_bp}{pos} = $param{-POS}; $self->{second_bp}{cover} = $param{-COVER}; $self->{second_bp}{sc_seq} = $param{-SC_SEQ}; $self->{second_bp}{chr} = $param{-CHR}; } return $self->{second_bp}; } sub type { my $self = shift; my ($bp1, $bp2) = ($self->{first_bp}, $self->{second_bp}); if($bp1->{left_chr} ne $bp1->{right_chr}) { return CTX if($bp1->{left_ort} eq $bp2->{left_ort} && $bp1->{right_ort} eq $bp2->{right_ort}); return UNK; } if( $bp1->{left_ort} eq $bp1->{right_ort} && $bp2->{left_ort} eq $bp2->{right_ort} ) { # Insertion or deletion return DEL if( $bp1->{left_pos} <= $bp1->{right_pos} && $bp2->{left_pos} <= $bp2->{right_pos}); return INS if( $bp1->{left_pos} >= $bp1->{right_pos} && $bp2->{left_pos} >= $bp2->{right_pos}); return UNK; } if( $bp1->{left_ort} ne $bp1->{right_ort} && $bp2->{left_ort} ne $bp2->{right_ort} ) { return INV if( ($bp1->{left_ort} eq '+' && $bp2->{left_ort} eq '-') || ($bp2->{left_ort} eq '+' && $bp1->{left_ort} eq '-')); return ITX if( ($bp1->{left_ort} eq '+' && $bp2->{left_ort} eq '+') || ($bp1->{left_ort} eq '-' && $bp2->{left_ort} eq '-') ); return UNK; } return UNK; } sub to_string { my $self = shift; my $type = $self->type; my ($bp1, $bp2) = ($self->{first_bp}, $self->{second_bp}); my ($left_len, $right_len) = (0, 0); if(exists $bp1->{left_len}) { ($left_len, $right_len ) = ($bp1->{left_len}, $bp1->{right_len}); } else { $left_len = length($bp1->{sc_seq}) if($bp1->{sc_seq}); $right_len = length($bp2->{sc_seq}) if($bp2->{sc_seq}); } if($type == INV && $bp1->{pos} > $bp2->{pos}) { ($bp1, $bp2) = ($bp2, $bp1); ($left_len, $right_len) = ($right_len, $left_len); } my ($cover1, $cover2) = ($bp1->{cover}, $bp2->{cover}); my ($chrA, $chrB, $ortA, $ortB) = ($bp1->{left_chr}, $bp1->{right_chr}, $bp1->{left_ort}, $bp1->{right_ort}); my ($posA, $posB, $countA, $countB) = ($bp1->{pos}, $bp2->{pos}, $bp1->{sc_reads}, $bp2->{sc_reads}); if($bp1->{chr} eq $bp1->{right_chr} && $bp1->{pos} == $bp1->{right_pos}) { $posA = $bp2->{pos}; $posB = $bp1->{pos}; $countA = $bp2->{sc_reads}; $countB = $bp1->{sc_reads}; ($cover1, $cover2) = ($cover2, $cover1); ($left_len, $right_len) = ($right_len, $left_len); } my $rtn = join("\t", ($chrA, $posA, $ortA, $countA, $chrB, $posB, $ortB, $countB, $type2str{$type}, $cover1, $cover2, $left_len, $right_len, $self->get_statistics(100), $self->{c_start}, $self->{start_chr}, $self->{t_start}, $self->{c_end}, $self->{end_chr}, $self->{t_end}, $self->{consensus})); if($self->{type} == INV) { $rtn = join("\t", ($rtn, $self->{c_start2}, $self->{start_chr2}, $self->{t_start2}, $self->{c_end2}, $self->{end_chr2}, $self->{t_end}, $self->{consensus2})); } return $rtn; } sub set_consensus { my $self = shift; my ($validator, $paired ) = @_; if(!$assembler) { print STDERR "No assembler set, no consensus generated\n"; return; } my %all_reads; my ($bp1, $bp2) = ($self->{first_bp}, $self->{second_bp}); my $N = 0; my @s_reads1 = $self->_get_reads($sam_d, $bp1); my @s_reads2 = $self->_get_reads($sam_d, $bp2); if($self->{type} == INV) { $self->{consensus} = _generate_consensus(\@s_reads1); $self->{consensus2} = _generate_consensus(\@s_reads2); } else { push @s_reads1, @s_reads2; $self->{consensus} = _generate_consensus(\@s_reads1); } } sub _generate_consensus { my $s_reads = shift; my $N = scalar(@{$s_reads}); return $s_reads->[0]{seq} if($N == 1); my $fa_name = prepare_reads($s_reads); my ($contig_file, $sclip_count) = $assembler->run($fa_name); if(!$contig_file || -s $contig_file == 0) { system ("rm $fa_name"); system("rm $fa_name*"); return; } my $n = 0; my $contig_name; foreach my $c (keys %{$sclip_count}) { if($sclip_count->{$c} > $n) { $n = $sclip_count->{$c}; $contig_name = $c; } } return if($N * 8 > $n * 10); my $contig_seqs = read_fa_file($contig_file); return $contig_seqs->{$contig_name}; } sub _get_reads{ my $self = shift; my ($sam, $bp) = @_; my ($chr, $pos) = ($bp->{chr}, $bp->{pos}); my ($start, $end) = ($pos, $pos); if($bp->{all_pos}) { my @tmp = @{$bp->{all_pos}}; @tmp = sort {$a <=> $b} @tmp; ($start, $end) = ($tmp[0], $tmp[$#tmp]); } my %reads; return if(scalar @{$bp->{reads}} == 0); foreach my $r (@{$bp->{reads}}){ $reads{$r} = 1; } #my ($sam, $chr, $start, $end, $reads) = # ($args{-SAM}, $args{-CHR}, $args{-START}, $args{-END}, $args{-READS}) ; my @rtn; my $range = $chr; $range = $chr . ":" . $start . "-" . $end; my($s, $e) = ($start, $end); $sam->fetch($range, sub { my $a = shift; return unless (exists $reads{$a->qname}); return unless ($a->start && $a->end); return unless (($a->start >= $start && $a->start <= $end) || ($a->end >= $start && $a->end <= $end)); $s = $a->start if($s > $a->start); $e = $a->end if($e < $a->end); my $qscore = $a->qscore; delete $reads{$a->qname}; push @rtn, { name => $a->qname, seq => $a->query->dna, qual => $qscore, }; } ); # $bp->{range} = [$s, $e]; if($self->{type} == INV) { if($start == $bp->{left_pos} || $end == $bp->{left_pos}) { $bp->{left_range} = [$s, $e]; } else { $bp->{right_range} = [$s, $e]; } return @rtn; } if($chr eq $self->{first_bp}{left_chr} && ($start == $self->{first_bp}{left_pos} || $end == $self->{first_bp}{left_pos})){ $self->{first_bp}{left_range} = [$s, $e]; } if($chr eq $self->{first_bp}{right_chr} && ($start == $self->{first_bp}{right_pos} || $end == $self->{first_bp}{right_pos})){ $self->{first_bp}{right_range} = [$s, $e]; } return @rtn; } my $start_dir; BEGIN { $start_dir = getcwd; } sub _cat_genes { my ($ort, @genes) = @_; my $rtn; my @names; foreach my $g (@genes) { push @names, $g->val->name; } @names = uniq @names; $rtn = join(";", @names); $rtn = $rtn . "($ort)" if($rtn); return $rtn; } sub get_genes { my $self = shift; return ['NA', 'NA', 'NA'] unless $gm; my ($gene1, $gene2); my $dist; my ($bp1, $bp2) = ($self->{first_bp}, $self->{second_bp}); my ($ort1, $ort2) = ($bp1->{left_ort}, $bp1->{right_ort}); my ($chr1, $chr2) = ($bp1->{left_chr}, $bp1->{right_chr}); my ($pos1, $pos2) = ($bp1->{pos}, $bp2->{pos}); ($pos1, $pos2) = ($pos2, $pos1) if($bp1->{right_pos} == $pos1); my $range1 = $ort1 eq "+" ? [$pos1 - 5, $pos1] : [$pos1, $pos1 + 5]; my $range2 = $ort2 eq "+" ? [$pos2, $pos2 + 5] : [$pos2 - 5, $pos2]; my ($tmp1, $tmp2) = ($chr1 =~ m/chr/ ? $chr1 : "chr" . $chr1, $chr2 =~ m/chr/ ? $chr2 : "chr" . $chr2); my ($r_tree1, $f_tree1) = ($gm->sub_model($tmp1, "-"), $gm->sub_model($tmp1, "+")); my ($r_tree2, $f_tree2) = ($gm->sub_model($tmp2, "-"), $gm->sub_model($tmp2, "+")); my ($g_ort1, $g_ort2); my @genes; @genes = $r_tree1->intersect($range1) if($r_tree1); $gene1 = _cat_genes("-", @genes) if(scalar @genes > 0 ); undef @genes; @genes = $f_tree1->intersect($range1) if($f_tree1); if(scalar @genes > 0) { my $tmp = _cat_genes("+", @genes); if($gene1) { $gene1 .= ";" . $tmp; } else { $gene1 = $tmp; } } undef @genes; @genes = $r_tree2->intersect($range2) if($r_tree2); $gene2 = _cat_genes("-", @genes) if(scalar @genes > 0 ); undef @genes; @genes = $f_tree2->intersect($range2) if($f_tree2);; if(scalar @genes > 0) { my $tmp = _cat_genes("+", @genes); if($gene2) { $gene2 .= ";" . $tmp; } else { $gene2 = $tmp; } } $gene1 = 'NA' unless $gene1; $gene2 = 'NA' unless $gene2; my $type = $self->{type}; if( $type == INS or $type == DEL ) { ($pos1, $pos2) = ($pos2, $pos1) if($pos1 > $pos2); @genes = $r_tree1->intersect([$pos1 - 5, $pos2 + 5]); if(scalar @genes >= 2) { $dist = scalar(@genes) - 2; $dist .= "(-)"; } @genes = $f_tree1->intersect([$pos1 - 5, $pos2 + 5]); if(scalar @genes >= 2) { if($dist) { $dist .= ":" . (scalar (@genes) - 2); } else { $dist = scalar (@genes) - 2; } $dist .= "(+)"; } } $dist = 'NA' unless $dist; return [$gene1, $gene2, $dist]; } sub to_full_string { my $self = shift; my $type = $self->{type}; my ($bp1, $bp2) = ($self->{first_bp}, $self->{second_bp}); return join("\t", ( $bp1->{left_chr}, $bp1->{left_pos}, $bp1->{left_ort}, $bp1->{right_chr}, $bp1->{right_pos}, $bp1->{right_ort}, $bp1->{sc_reads}, $bp2->{left_chr}, $bp2->{left_pos}, $bp2->{left_ort}, $bp2->{right_chr}, $bp2->{right_pos}, $bp2->{right_ort}, $bp2->{sc_reads}, $type2str{$type})); } sub filter { my $self = shift; print STDERR "SV filter starting....\n"; $self->{type} = $self->type; $self->set_consensus; if(!$self->{consensus} || length $self->{consensus} < $read_len * $min_percent_cons_of_read) { $self->{error} = "No Consensus or consensus too short"; print STDERR "FAILED\n"; return; } if($self->{type} == INV && (!$self->{consensus2} || length $self->{consensus2} < $read_len * $min_percent_cons_of_read)) { $self->{error} = "No Consensus or consensus too short"; print STDERR "FAILED\n"; return; } foreach my $f (values(%default_filters) ){ if(! $f->($self)){ print STDERR "FAILED\n"; return; } } print STDERR "PASSED\n"; return 1; } sub _mapping_quality_filter { my $self = shift; if($self->{type} == INV) { my $tmp1 = $self->_mapping_quality($self->{first_bp}, $self->{consensus}, 1); my $tmp2 = $self->_mapping_quality($self->{second_bp}, $self->{consensus2}, 2); return $tmp1 && $tmp2; } return $self->_mapping_quality($self->{first_bp}, $self->{consensus}, 1); } sub _mapping_quality { my $self = shift; my $bp = shift; my $con = shift; my $which = shift; $which = "" unless $which == 2; my $l = length($con); print STDERR "Mapping quality filter ... "; open my $QUERY, ">query.fa" or croak "can't open query.fa : $OS_ERROR"; print $QUERY ">query\n", $con, "\n"; close $QUERY; my ($range1, $range2) = $self->_find_bp_ref_range($bp, $con); my ($s1, $e1, $t_s1, $t_e1) = $self->_map_consensus($range1); return unless $e1; my ($chr, $s, $e) = split /[:|-]/, $range2; my $offset = 0; if($s < $t_e1 && $e > $t_s1) { #overlap my $tmp = substr $con, 0, $s; if($s1 < $l - $e1) { $offset = $e1; $tmp = substr $con, $e1 + 1; } open my $QUERY, ">query.fa" or croak "can't open query.fa : $OS_ERROR"; print $QUERY ">query\n", $tmp, "\n"; close $QUERY; } my ($s2, $e2, $t_s2, $t_e2) = $self->_map_consensus($range2); return unless $e2; $s2 += $offset; $e2 += $offset; if($s1 < $s2) { if($e1 < $s2) { $self->{"insert" . $which} = substr($con, $e1, $s2 - $e1); } $self->{"c_start" . $which} = $s1; $self->{"t_start" . $which} = $t_s1; $self->{"start_chr" . $which} = $bp->{left_chr}; $self->{"c_end" . $which} = $e2; $self->{"t_end" . $which} = $t_e2; $self->{"end_chr". $which} = $bp->{right_chr}; $s1 = 1; ($e1, $s2) = ($s2, $e1); $e2 = $l; } else { if($e2 < $s1) { $self->{"insert" . $which} = substr($con, $e2, $s1 - $e2); } $self->{"c_start" . $which} = $s2; $self->{"t_start" . $which} = $t_s2; $self->{"start_chr" . $which} = $bp->{right_chr}; $self->{"c_end" . $which} = $e1; $self->{"t_end" . $which} = $t_e1; $self->{"end_chr" . $which } = $bp->{left_chr}; $s2 = 1; ($e2, $s1) = ($s1, $e2); $e1 = $l; } my $n = 1; foreach my $r( ($range1, $range2) ) { my $r_a = $r eq $range1 ? $range2 : $range1; my($chr, $s, $e) = split /[:|-]/, $r; my($chr_a, $s_a, $e_a) = split /[:|-]/, $r_a; my($s_c, $e_c) = $n == 1 ? ($s1, $e1) : ($s2, $e2); $n++; open my $QUERY, ">query.fa" or croak "can't open query.fa : $OS_ERROR"; print $QUERY ">query\n", substr($self->{consensus}, $s_c, $e_c - $s_c + 1), "\n"; close $QUERY; my $hit = $aligner->run(-TARGET => $genome . ":" . $r, -QUERY => "query.fa"); my $h = $hit->{query}; return unless $h; my $hsp = $h->next_hsp; my @matches = $hsp->matches; $hit = $mapper->run(-QUERY => "query.fa"); foreach $h (@{$hit->{query}}) { next if($h->{tchr} eq $chr && $h->{tstart} >= $s - $l && $h->{tend} <= $e + $l); # the same one return if($h->{matches} >= $matches[0]); #find a better or as good as match return if($h->{tchr} eq $chr_a && $h->{matches} - $matches[0] >= -5 && $self->{type} == CTX); } } print STDERR "PASSED\n"; return 1; } sub _map_consensus { my $self = shift; my $range = shift; my ($s_c, $e_c, $s_t, $e_t); my $hits = $aligner->run(-TARGET => $genome . ":" . $range, -QUERY => "query.fa"); my ($chr, $s, $e) = split /[:|-]/, $range; my $h = $hits->{query}; return unless $h; # can't find a good enough match my $hsp = $h->next_hsp; my @matches = $hsp->matches; ($s_c, $e_c, $s_t, $e_t) = ($hsp->start('query'), $hsp->end('query'), $hsp->start('hit'), $hsp->end('hit')); return if( ($e_c - $s_c + 1) * 0.97 > $matches[0]); # the alignment is not good ($s_t, $e_t) = $hsp->strand == 1 ? ($s_t + $s, $e_t + $s) : ($e_t + $s, $s_t + $s); return ($s_c, $e_c, $s_t, $e_t); } sub _find_bp_ref_range { my $self = shift; my $bp = shift; my $con = shift; my $l = int(length($con)/2); my $ext = $l * 2; $ext = 100000 if($RNASeq); if($self->{type} == DEL && abs($bp->{left_pos} - $bp->{right_pos}) < $l ){ $l = abs($bp->{left_pos} - $bp->{right_pos}) - 1; } my($range1, $range2); my ($chr, $p) = ($bp->{left_chr}, $bp->{left_pos}); my $ort = $bp->{left_ort}; my ($s, $e); if($bp->{left_range} ) { $s = $bp->{left_range}->[0] - $l; $e = $bp->{left_range}->[1] + $l; } else { $s = $e = $p; $s = $ort eq "+" ? $p - $ext : $p - $l; $e = $ort eq "+" ? $p + $l : $p + $ext; } $s = 1 if($s < 1); $e = $bp->{left_pos} + $l if($self->{type} == DEL); $range1 = $chr . ":" . $s . "-" . $e; ($chr, $p) = ($bp->{right_chr}, $bp->{right_pos}); $ort = $bp->{left_ort}; if($bp->{right_range} ) { $s = $bp->{right_range}->[0] - $l; $e = $bp->{right_range}->[1] + $l; } else { $s = $e = $p; $s = $ort eq "-" ? $p - $ext : $p - $l; $e = $ort eq "-" ? $p + $l : $p + $ext; } $s = $bp->{right_pos} - $l if($self->{type} == DEL); $s = 1 if($s < 1); $range2 = $chr . ":" . $s . "-" . $e; return ($range1, $range2); } sub _find_ref_range { my $self = shift; my $l = int(length($self->{consensus})/2); my ($range1, $range2); my ($bp1, $bp2) = ($self->{first_bp}, $self->{second_bp}); my ($chr, $p) = ($bp1->{chr}, $bp1->{pos}); my ($s, $e); my $ext = $l*2 ; $ext = 100000 if($RNASeq); if($self->{type} == DEL && abs($bp1->{pos} - $bp2->{pos}) < $l ){ $l = abs($bp1->{pos} - $bp2->{pos}) - 1; } my $ort = $bp1->{pos} == $bp1->{left_pos} ? ($bp1->{left_ort} eq "+" ? "-" : "+") : $bp1->{right_ort}; if($bp1->{range}) { $s = $bp1->{range}->[0] - $l; $s = 1 if($s < 1); $e = $bp1->{range}->[1] + $l; } else { $s = $e = $p; $s = $ort eq "+" ? $p - $l : $p - $ext; $e = $ort eq "+" ? $p + $ext : $p + $l; } $s = 1 if($s < 1); $e = $bp1->{pos} + $l if($self->{type} == DEL); $range1 = $chr . ":" . $s . "-" . $e; ($chr, $p) = ($bp2->{chr}, $bp2->{pos}); $ort = $bp2->{pos} == $bp2->{left_pos} ? ($bp2->{left_ort} eq '+' ? '-' : '+') : $bp2->{right_ort}; if($self->{type} == INV) { $ort = $bp1->{pos} == $bp1->{left_pos} ? $bp1->{right_ort} : ($bp1->{left_ort} eq '+' ? '-' : '+'); } if($bp2->{range} && $self->{type} != INV) { $s = $bp2->{range}->[0] - $l; $e = $bp2->{range}->[1] + $l; } else { $s = $e = $p; $s = $ort eq "+" ? $p - $l : $p - $ext; $e = $ort eq "+" ? $p + $ext : $p + $l; } $s = $bp2->{pos} - $l if($self->{type} == DEL); $s = 1 if($s < 1); $range2 = $chr . ":" . $s . "-" . $e; return($range1, $range2); } my $GAP = -5; my $MATCH = 2; my $MISMATCH = -5; sub _refine_bp { my $self = shift; my $h = shift; my $ref_seq = shift; my $con = $self->{consensus}; my $hsp = $h->next_hsp; my ($i, $j) = ($hsp->start("hit"), $hsp->start("query")); if($hsp->strand == -1) { $con = rev_comp($con); $j = length($con) - $hsp->end("query"); } my ($score, $g, $max_score, $s) = (0, 0, 0); my ($current_s_r, $current_s_c) = ($i, $j); my ($s_r, $s_c, $e_r, $e_c); my $p; my ($n_matches, $n_max_matches) = (0, 0); my @bl_r = @{$hsp->gap_blocks('hit')}; my @bl_c = @{$hsp->gap_blocks('query')}; for( my $n = 0; $n < scalar @bl_r; $n++) { my $m = $bl_r[$n]->[1]; if($p) { #gaps if (!$RNASeq || $bl_r[$n]->[0] - $p < 25) { $score += $GAP * ($bl_r[$n]->[0] - $p); $score = 0 if($score < 0 ) } $i += $m; $j += $bl_c[$n]->[1]; } while($m) { #matches / mismatches ($current_s_r, $current_s_c, $n_matches) = ($i, $j, 0) if($score == 0); #reset if(substr($ref_seq, $i, 1) eq substr($con, $j, 1)) { $score += $MATCH; $n_matches++; } else { $score += $MISMATCH; } if($score > $max_score) { $n_max_matches = $n_matches; $max_score = $score; ($s_r, $s_c) = ($current_s_r, $current_s_c); ($e_r, $e_c) = ($i, $j); } $n_matches = $score = 0 if($score < 0 ); $m--; $i++; $j++; } $p = $bl_r[$n]->[0] + $bl_r[$n]->[1]; } return($n_max_matches, $s_r, $e_r, $s_c, $e_c); } sub _mapping_artifact_filter { my $self = shift; if($self->{type} == INV) { return ($self->_mapping_artifact($self->{consensus}) || $self->_mapping_artifact($self->{consensus2}) ); } return $self->_mapping_artifact($self->{consensus}); } sub _mapping_artifact { my $self = shift; my $con = shift; my $l = length $con; print STDERR "Mapping artifact filter ... "; $self->{error} = "Mapping Artifact"; $l = $l - 5; my $options = " minIdentity=97 -out=psl -nohead"; $options = $options . " -maxIntron=5 " unless $RNASeq; my $exp = $l; if($self->{type} == DEL && $RNASeq) { $exp = $self->{second_bp}{pos} - $self->{first_bp}{pos}; $options = $options . " -maxIntron=" . ($exp + 100) if($exp > 75000); } open my $QUERY, ">query.fa" or croak "can't open query.fa : $OS_ERROR"; print $QUERY ">query\n", $con, "\n"; close $QUERY; my $tmp_mapping = $mapper->run(-QUERY => "query.fa", -OPTIONS => $options); system("rm query.fa"); system("rm query.fa*"); $l = $l + 5; if($RNASeq) { #can't find a decent mapping for DEL event return if($self->{type} == DEL && !$tmp_mapping->{query}); foreach my $h(@{$tmp_mapping->{query}}) { #next if($h->{tend} - $h->{tstart} < $exp - 10); return if ($self->{type} != DEL && $h->{qend} - $h->{qstart} > $l - 10); my $chr = $h->{tchr}; $chr = "chr" . $chr unless $chr =~ m/^chr/; my ($r_tree, $f_tree) = ($gm->sub_model($chr, "-"), $gm->sub_model($chr, "+")); return unless ($f_tree && $r_tree); my @f_genes = $f_tree->intersect([$h->{tstart}, $h->{tend}]); my @r_genes = $r_tree->intersect([$h->{tstart}, $h->{tend}]); return if(scalar @f_genes <= 1 && scalar @r_genes <= 1); my @f_tmp_genes; my @r_tmp_genes; foreach my $g (@f_genes) { foreach my $block (@{$h->{blocks}}) { if($g->val->overlap($block)){ push @f_tmp_genes, $g; last; } } } foreach my $g (@r_genes) { foreach my $block (@{$h->{blocks}}) { if($g->val->overlap($block)){ push @r_tmp_genes, $g; last; } } } return if(scalar @f_tmp_genes < 1 && scalar @r_tmp_genes < 1); } } else { foreach my $h (@{$tmp_mapping->{query}}){ return if($h->{tend} - $h->{tstart} < $l + 10 && $h->{qend} - $h->{qstart} > $l - 5); } } $self->{error} = undef; return 1; } sub _germline_sclip_filter { my $self = shift; print STDERR "Germline sclip filter\n"; return 1 if(!$sam_g); $self->{error} = "Germline Event"; my $rtn = _compare_seq($self->{first_bp}) && _compare_seq($self->{second_bp}); $self->{error} = undef if($rtn); return $rtn; } sub _compare_seq { my $bp = shift; my $seq; if($bp->{pos} == $bp->{left_pos}) { my $seg = $sam_d->segment($bp->{right_chr}, $bp->{right_pos} - $germline_seq_width, $bp->{right_pos} + $germline_seq_width); $seq = $seg->dna; } else { my $seg = $sam_d->segment($bp->{left_chr}, $bp->{left_pos} - $germline_seq_width, $bp->{left_pos} + $germline_seq_width); $seq = $seg->dna; } my $sclip_seq = _get_sclip_seqs($sam_g, $bp->{chr}, $bp->{pos} - $germline_search_width, $bp->{pos} + $germline_search_width); open my $TARGET, ">target.fa" or croak "can't open target.fa : $OS_ERROR"; print $TARGET ">target\n", $seq, "\n"; close $TARGET; if(keys(%{$sclip_seq})) { open my $QUERY, ">query.fa" or croak "can't open query.fa : $OS_ERROR"; foreach my $s (keys(%{$sclip_seq})) { print $QUERY ">", $s, "\n", $sclip_seq->{$s}, "\n"; } close($QUERY); my $hits = $aligner->run(-TARGET => "target.fa", -QUERY => 'query.fa'); return if(keys(%{$hits})); } return 1; } sub _type_distance_filter { my $self = shift; my $type = $self->{type}; $self->{error} = "Type Distance"; print STDERR "Type distance filter\n"; if($type == UNK) { #those events are not sure, always ignore them print STDERR $self->to_full_string, "\n"; return; } my ($bp1, $bp2) = ($self->{first_bp}, $self->{second_bp}); return 1 if($bp2->{sc_reads} == 0); my ($d1, $d2); if($type == INS || $type == DEL) { ($d1, $d2) = ($bp1->{left_pos} - $bp2->{left_pos}, $bp1->{right_pos} - $bp2->{right_pos}); } if( $type == INV ){ ($d1, $d2) = ($bp1->{left_pos} - $bp2->{left_pos}, $bp2->{right_pos} - $bp1->{right_pos}); } if( $type == ITX ) { ($d1, $d2) = ($bp1->{left_pos} - $bp2->{right_pos}, $bp2->{left_pos} - $bp1->{right_pos}); } if($type == CTX ) { if($bp1->{left_ort} eq $bp1->{right_ort} || $bp1->{sc_reads} == 0 || $bp2->{sc_reads} == 0) { ($d1, $d2) = ($bp1->{left_pos} - $bp2->{left_pos}, $bp1->{right_pos} - $bp2->{right_pos}) ; } else { ($d1, $d2) = ($bp1->{left_pos} - $bp2->{right_pos}, $bp2->{left_pos} - $bp1->{right_pos}); } } print STDERR $self->to_full_string, "\t", $d1, "\t", $d2, "\n"; if($d1 <= 1 && $d2 <= 1 || abs($d1 - $d2) <= 5) { $self->{error} = undef; return 1; } if(abs($d1) > $max_bp_dist || abs($d2) > $max_bp_dist){ print STDERR "Distance fail\n"; return; } $self->{error} = undef; return 1; # the distance is small enough } sub _germline_indel_filter { my $self = shift; my $type = $self->{type}; print STDERR "Germline INDEL FILTER test\n"; return if(abs($self->{first_bp}{pos} - $self->{second_bp}{pos}) < 40); return 1 if(!$sam_g); return 1 if( $type != INS && $type != DEL); return 1 if( abs($self->{first_bp}{pos} - $self->{second_bp}{pos}) > 50 ); $self->{error} = "Germline INDEL"; my ($start, $end ) = ($self->{first_bp}{left_pos}, $self->{first_bp}{right_pos}); my $indel_len = abs($end - $start); ($start, $end) = ($end, $start) if($start > $end); my $itr = $sam_g->features(-iterator => 1, -seq_id => $self->{first_bp}{chr}, -start => $start, -end => $end); while( my $a = $itr->next_seq ) { next if($type == DEL && $a->cigar_str !~ m/D/); next if($type == INS && $a->cigar_str !~ m/I/); my $cigar_array = $a->cigar_array; my $pos = $a->pos; foreach my $ca (@{$cigar_array}) { if($ca->[0] eq 'M') { $pos += $ca->[1]; next; } if($ca->[0] eq 'I' && $type == INS ){ return if(abs($ca->[1] - $indel_len) <= 20 && abs($pos - $start) <= 20); next; } if($ca->[0] eq 'D' && $type == DEL) { return if(abs($ca->[1] - $indel_len) <= 20 && abs($pos - $start) <= 20); $pos += $ca->[1]; next; } } } $self->{error} = undef; return 1; } sub _tandem_repeat_filter { my $self = shift; my $con = $self->{consensus}; print STDERR "low compexity filter\n"; open my $QUERY, ">query.fa" or croak "can't open query.fa : $OS_ERROR"; print $QUERY ">query\n", $self->{consensus}, "\n"; close $QUERY; system("ptrfinder -seq query.fa -repsize $tr_min_size,$tr_max_size -minrep $tr_min_num > query.fa.rep"); if(-e "query.fa.rep") { open my $REP, "<query.fa.rep" or croak "can't open query.fa.rep:$OS_ERROR"; while( my $line = <$REP>) { chomp $line; my($pattern, $len, $times, $s, $e) = $line =~ m/PATTERN\s(\w+)\sLENGTH\s(\d+)\sTIMES\s(\d+)\sSTART\s(\d+)\sSTOP\s(\d+)\sID/; if($self->{type} == DEL || $self->{type} == INS){ if(abs($self->{first_bp}{left_pos} - $self->{first_bp}{right_pos}) < $tr_max_indel_size) { print STDERR "Tandem repeat mediated INDEL!\n"; close $REP; return; } } else{ if(($s < 5 || length($self->{consensus}) - $e < 5) && $len * $times > 30 ) { print STDERR "Tandem repeat mediated events!\n"; close $REP; return; } } } } return 1; } sub _get_sclip_seqs { my ($sam, $chr, $start, $end) = @_; my %rtn; my $range = $chr . ":" . $start . "-" . $end; $sam->fetch($range, sub { my $a = shift; my $cigar_str = $a->cigar_str; return if($cigar_str !~ /S/); my ($sclip_len, $pos, $seq, $qual); my @cigar_array = @{$a->cigar_array}; if($cigar_array[0]->[0] eq 'S' ) { $sclip_len = $cigar_array[0]->[1]; $pos = $a->start; return if($pos < $start or $pos > $end); # the softclipping position is not in range $seq = substr($a->query->dna, 0, $sclip_len ); $rtn{$a->qname} = $seq if($sclip_len >= 10); } #if($cigar_str =~ m/S(\d+)$/) { if($cigar_array[$#cigar_array]->[0] eq 'S') { $sclip_len = $cigar_array[$#cigar_array]->[1]; $pos = $a->end; return if($pos < $start or $pos > $end); # the softclipping position is not in range $seq = substr($a->qseq, $a->l_qseq - $sclip_len ); $rtn{$a->qname} = $seq if($sclip_len >= 10); } } ); return \%rtn; } sub _RNASeq_strand_filter { my $self = shift; my $type = $self->type; print STDERR "RNASeq strand filter\n"; return 1 unless $gm; my ($bp1, $bp2) = ($self->{first_bp}, $self->{second_bp}); my ($ort1, $ort2) = ($bp1->{left_ort}, $bp1->{right_ort}); my ($chr1, $chr2) = ($bp1->{left_chr}, $bp1->{right_chr}); my ($pos1, $pos2) = ($bp1->{pos}, $bp2->{pos}); ($pos1, $pos2) = ($pos2, $pos1) if($bp1->{right_pos} == $pos1); my ($tmp1, $tmp2) = ($chr1 =~ m/chr/ ? $chr1 : "chr" . $chr1, $chr2 =~ m/chr/ ? $chr2 : "chr" . $chr2); $tmp1 = "chrM" if($tmp1 eq "chrMT"); $tmp2 = "chrM" if($tmp2 eq "chrMT"); my ($r_tree1, $f_tree1) = ($gm->sub_model($tmp1, "-"), $gm->sub_model($tmp1, "+")); my ($r_tree2, $f_tree2) = ($gm->sub_model($tmp2, "-"), $gm->sub_model($tmp2, "+")); my ($g_ort1, $g_ort2); return 1 unless($r_tree1 && $r_tree2 && $f_tree1 && $f_tree2); $g_ort1 = "-" if(scalar($r_tree1->intersect([$pos1 - 5, $pos1])) > 0); $g_ort1 = "+" if(scalar($f_tree1->intersect([$pos1 - 5, $pos1])) > 0); $g_ort2 = "-" if(scalar($r_tree2->intersect([$pos2, $pos2 + 5])) > 0); $g_ort2 = "+" if(scalar($f_tree2->intersect([$pos2, $pos2 + 5])) > 0); return 1 unless($g_ort1 && $g_ort2); return 1 if($g_ort1 eq $ort1 && $g_ort2 eq $ort2); return 1 if($g_ort1 ne $ort1 && $g_ort2 ne $ort2); return; } # INS filter check to make sure that the INS part overlap with any gene # if the INS part only in intron will return as a false positive sub _RNASeq_INS_filter { my $self = shift; my $type = $self->{type}; return 1 if($type != INS); print STDERR "RNAseq INS filter\n"; my ($bp1, $bp2) = ($self->{first_bp}, $self->{second_bp}); my ($pos1, $pos2) = ($bp1->{pos}, $bp2->{pos}); ($pos1, $pos2) = ($pos2, $pos1) if($pos2 < $pos1); my $chr = $bp1->{chr}; $chr = "chr" . $chr unless $chr =~ m/chr/; my ($r_tree, $f_tree) = ($gm->sub_model($chr, "-"), $gm->sub_model($chr, "+")); foreach my $tree( ($r_tree, $f_tree) ) { my @tmp; @tmp = $f_tree->intersect([$pos1, $pos2]); foreach my $g (@tmp) { return 1 if($g->val->overlap([$pos1, $pos2])); } } return; } sub _RNASeq_DEL_filter { my $self = shift; my $type = $self->type; return 1 if($type != DEL); print STDERR "RNAseq DEL filter\n"; my ($gene1, $gene2, $dist) = @{$self->get_genes}; return if($gene1 eq $gene2); my ($d_plus, $d_minus) = (0, 0); $d_plus = $1 if($dist =~ m/(\d+)\(\+\)/); $d_minus = $1 if($dist =~ m/(\d+)\(\-\)/); return if($d_plus == 0 && $d_minus == 0); my ($bp1, $bp2) = ($self->{first_bp}, $self->{second_bp}); return if($bp1->{cover} == 0 || $bp2->{cover} == 0); my($left_len, $right_len); if(exists $bp1->{left_len}) { ($left_len, $right_len ) = ($bp1->{left_len}, $bp1->{right_len}); } else { $left_len = length($bp1->{sc_seq}) if($bp1->{sc_seq}); $right_len = length($bp2->{sc_seq}) if($bp2->{sc_seq}); } return if($left_len < 30 || $right_len < 30); return if($bp1->{sc_reads} < 10 || $bp2->{sc_reads} < 10); return unless $bp1->{left_gene}->overlap([$bp1->{pos} - $left_len, $bp1->{pos}]); return unless $bp1->{right_gene}->overlap([$bp2->{pos} + $right_len, $bp2->{pos}]); return 1; } sub get_statistics { my $self = shift; my $half_width = shift; my $sam = $self->sam_d; my @rtn; foreach my $bp ( ($self->{first_bp}, $self->{second_bp})) { my ($chr, $pos ) = ($bp->{chr}, $bp->{pos}); my $range = $chr . ":" . ($pos - $half_width) . "-" . ($pos + $half_width); my ($n_seq, $n_rep, $total_pid) = (0, 0, 0); $sam->fetch($range, sub { my $a = shift; return unless ($a->start && $a->end); return unless ($a->start >= $pos - $half_width && $a->end <= $pos + $half_width); $n_seq++; $total_pid += _cal_pid($a); if($a->has_tag("XT")) { $n_rep++ if($a->aux_get("XT") ne "U"); } } ); if($n_seq == 0) { push @rtn, (0, 0); } else { push @rtn, ($total_pid/$n_seq, $n_rep/$n_seq); } } return @rtn; } sub _cal_pid { my $a = shift; my ($ref, $matches, $query) = $a->padded_alignment; my ($n_match, $n) = (0, 0); for( my $i = 0; $i < length($matches); $i++) { my $c = substr $matches, $i, 1; $n_match++ if($c eq "|"); $n++; } return 0 if($n == 0); return $n_match/$n; } 1; __END__; =pod =head1 NAME