RSS Git Download  Clone
Raw Blame History
#!/usr/bin/env perl

=begin
analyses patient sample vcf files for variant frequencies across multiple runs.
Produces 1 table + 1 graph for each sample in each of n = 1, 2, 3, etc directories.
n values correspond to frequency each variant found across multiple runs, so in
n = 1, all html tables list variants found only once for that sample across all
runs analysed. In n = 2, tables list variants found in 2 out of total number of
runs, etc. Each variant can appear >= once per vcf file (html table).
=cut

use lib '/home/raj/perl5/lib/perl5';

use Modern::Perl '2012';
use Data::Dumper::Concise;
use File::Path qw(make_path remove_tree); # IO::All PITA for dir manipulation
use Path::Tiny;
use FindBin; # warn $FindBin::Bin;
use IO::All;
use CGI;

use lib $FindBin::RealBin . '/../../lib';
use NGS::ChartMaker;
use DBIx::Simple;

my $src_dir = $FindBin::Bin . '/src/analyse';
my @dirs = io($src_dir)->all; # print "$_ is a " . $_->type . "\n" for @dirs; exit;
    
my %samples;
# collect filenames from each dir, add to %samples hash:
for my $d (@dirs) { # warn Dumper $_;
    next unless $d->type eq 'dir'; # skip eg png's
	my @files = io($d)->all; # Dumper \@files;  
	for my $f (@files) { # warn $f->filename;
		my $filename = $f->filename;
		next unless $filename =~ /vcf\Z/; # warn $f->filename;
		push @{ $samples{$filename} }, $f; # io object
	}
} # warn Dumper \%samples; exit;

# delete and recreate analysis output dirs, one for each src dir:
for ( my $i = 1; $i <= @dirs; $i++ ) { # warn $i;
    my $dir = $FindBin::Bin . '/analysis/' . $i;
    remove_tree($dir) if -e $dir;  # not worth the hassle using IO::All   
    make_path($dir); # ditto
}

my $dbix = _build_dbix(); # warn Dumper $dbix;
my $EXCLUDES = _get_excludes(); # warn Dumper $EXCLUDES;

my $q = CGI->new;
my %h = (); # start-position indexer (global, used in parse(), reset in FILE loop)

my %chart = (
    x_title => 'Depth',
    y_title => 'VF',
);
my %all_data;

# params to collect from vcf file:
my @params = qw( position vf alt ref gene filter depth runId);

FILE: # check each file is represented in all dirs:
while ( my($sample, $o) = each %samples ) {
	unless ( scalar @$o == scalar @dirs ) { # one object per file per directory
		printf "%s seen only %s times\n", $sample, scalar @$o;
		next FILE;
	}
    
	%h = (); # reset
	# parse file content in each directory, %h holds array of data
	for my $f(@$o) { # warn $f->name;
		parse($f);
	} # warn Dumper \%h;
	
	my %data; # array of all results (href) for this sample:
	while ( my($position, $d) = each %h ) { # warn Dumper $d;
		my ( $n, $results ) = ( $d->{count}, $d->{data} );

		for my $result(@$results) { 
			# add position to $result href:
			$result->{position} = $position;
			push @{ $data{$n} }, $result;
		}
	}

    $sample =~ s/\.vcf//; # warn $sample;
    
    # now have hash of all results from all dirs for this sample, keys = $n (1,2,3,etc)
    while ( my($n, $result) = each %data ) {
        my @rows = (); # reset
        $chart{data} = {}; # reset
        # sort each row by variation frequency (highest -> lowest):
        for my $row ( sort by_var_freq @$result ) {
            my $position = $row->{position};
            
            push @rows, $q->Tr( $q->td( [ @{$row}{@params} ] ) );
            
            push @{ $chart{data}{x} }, $row->{depth};
			push @{ $all_data{$n}{x} }, $row->{depth};
			
            push @{ $chart{data}{y} }, $row->{vf};
			push @{ $all_data{$n}{y} }, $row->{vf};
        } # warn Dumper \@rows; exit;

        my $total_variants = do {
            my %h; $h{ $_->{position} }++ for @$result;
            scalar keys %h;
        };
        my $content = do {
            my %d = (
                sample => $sample,
                total  => $total_variants,
                rows   => \@rows,
            );
            _template(\%d);
        };

        my $file = sprintf './analysis/%s/%s.html', $n, $sample;
        $content > io($file);        
        
        $chart{chart_title} = $sample; # warn Dumper scalar @{ $chart{data}{x} };
        $chart{data_title}  = sprintf '%s/%s', $n, $sample;
        NGS::ChartMaker->new(%chart)->make_chart();
    }
}

# output combined graphs, one each for n=1, n=2:
for (1,2) {
	my %h = (
		x_title => 'Depth',
		y_title => 'VF',
		chart_title => "All data $_",
		data_title => 'all_data_'.$_,
		data => $all_data{$_},
	); # warn Dumper scalar @{ $h{data}{y} };
	NGS::ChartMaker->new(%h)->make_chart();
}

# expand x-axis scale (0-4000) on n=1:
{
	my %h = (
		x_title => 'Depth',
		y_title => 'VF',
		max_x_val => 4000,
		chart_title => 'All data #1 expanded',
		data_title => 'all_data_1_expanded',
		data => $all_data{1},
	);
	NGS::ChartMaker->new(%h)->make_chart();
}

exit 0;

#===============================================================================

sub parse {
	my $f = shift; # io object
    
	my @folders = $f->splitdir; # warn $folders[-2]; # array of dirs in full path
	my @lines   = io($f->name)->slurp; # warn Dumper \@lines;
    
	my %local = (); # indexer localised to individual file

	LINE: for (@lines) {
		next LINE if /^#/; # skip comments
		my @fields = split "\t", $_;
		
		my $start_pos = $fields[1]; # warn $start_pos;
			# warn $start_pos if $EXCLUDES->{$start_pos};
		next LINE if (! $start_pos) || $EXCLUDES->{$start_pos};  
			# warn $start_pos if $EXCLUDES->{$start_pos};

        # initialise intra-file start position indexer:
		$local{$start_pos} ||= 1; # only need true/false, not item frequency
		
        { # add data as array(ref) - can have multiple start pos entries per file:
            my $data = _parse_fields(\@fields);
            # add run directory name:
            $data->{runId} = $folders[-2]; # warn $data->{runId}; # 2nd to last
            push @{ $h{$start_pos}{data} }, $data;
        }
	}
	# auto-increment %h start position key for each start position key in %i:
	for my $start_position (keys %local) { $h{$start_position}{count}++ }
}

sub by_var_freq {
	return $b->{vf} <=> $a->{vf} || $a->{depth} <=> $b->{depth};
}

sub _template {
    my $args = shift; # sample name (str), total (int), rows (arrayref)
    
    my ($sample, $total, $rows) = @{$args}{qw/sample total rows/};
    
    return sprintf <<"HTML", join "\n", @$rows;
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <style>
            html { font-family: verdana, sans-serif; }
            h2 { color: #800000; }
            h3 { }
            table { border-collapse: collapse; margin-left: 1em }
            td, th { border: solid 1px #c0c0c0; padding: 2px; font-size: 9pt; }
            #head { background-color: ActiveCaption; color: CaptionText; }
        </style>
      </head>
      <body>
        <h2>${sample}.vcf</h2>
        <h3>Total variants: $total</h3>
        <table>
          <tr id="head">
            <th>position</th>
            <th>vf</th>
            <th>alt</th>
            <th>ref</th>
            <th>gene</th>
            <th>filter</th>
            <th>depth</th>
            <th>run Id</th>
          <tr>
          <tbody>%s</tbody>
        </table>
      </body>
    </html>
HTML
}

sub _build_dbix {
    my $db = path($FindBin::RealBin, '..', '..', 'ngs.sqlite')->realpath;
	my $dsn = "dbi:SQLite:dbname=$db"; # warn 'DB:'. $db;

	my $dbix = DBIx::Simple->connect($dsn); # warn Dumper $dbix;
    return $dbix;
}

sub _get_excludes {
	$dbix->select('locations', ['position', 1], {action => 'exclude'})->map;
}

sub _parse_fields {
    my $data = shift; # arrayref of tab-separated vcf row entries

	my $ref     = $data->[3]; # warn $ref;
	my $alt     = $data->[4]; # warn $alt;
	my $filter  = $data->[6]; # warn $filter;
    my $info    = $data->[7]; # warn $info
	my $results = $data->[9]; # warn $results;
    
	my $vf = (split ':', $results)[3]; # warn $vf;
	my ($depth) = $info =~ /DP=(\d+)/; # warn $depth;
    my ($genes) = $info =~ /GI=(.*?)\;/; # warn $genes; # non-greedy match

    my $gene = do { # remove duplicate genes: eg TP53,TP53,TP53
        no warnings 'uninitialized'; # eg if regex failed due to malformed str
        my %seen = map { $_ => 1 } split ',', $genes;    
        join ',', keys %seen;
    } || '[UNDEF]';
    my %h = (
		filter => $filter,
		depth  => $depth,
        gene   => $gene,
		ref    => $ref,
		alt    => $alt,
		vf     => $vf,
	);
    return \%h;
}