]> git.donarmstrong.com Git - biopieces.git/blob - code_perl/Maasha/BGB/Track.pm
renaming BBrowser to BGB
[biopieces.git] / code_perl / Maasha / BGB / Track.pm
1 package Maasha::BGB::Track;
2
3 # Copyright (C) 2009 Martin A. Hansen.
4
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
9
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
19 # http://www.gnu.org/copyleft/gpl.html
20
21
22 # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> DESCRIPTION <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
23
24
25 # Routines for creating Biopieces Browser tracks.
26
27
28 # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
29
30
31 use warnings;
32 use strict;
33 use Data::Dumper;
34 use Maasha::Common;
35 use Maasha::Calc;
36 use Maasha::Filesys;
37 use Maasha::KISS;
38 use Maasha::Biopieces;
39 use Maasha::Seq;
40
41 use vars qw( @ISA @EXPORT );
42
43 @ISA = qw( Exporter );
44
45
46 # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
47
48
49 sub track_ruler
50 {
51     # Martin A. Hansen, November 2009.
52  
53     # Create a track with a ruler of tics and positions for
54     # the browser window.
55
56     my ( $cookie,   # browser cookie
57        ) = @_;
58
59     # Returns a list.
60
61     my ( $beg, $end, $factor, $step, $i, $txt, $x, @ruler );
62
63     $beg    = $cookie->{ 'NAV_START' };
64     $end    = $cookie->{ 'NAV_END' };
65     $factor = $cookie->{ 'IMG_WIDTH' } / ( $end - $beg + 1 );
66     
67     $step = 10;
68
69     while ( ( $end - $beg ) / $step > 20 ) {
70         $step *= 5;
71     }
72
73     for ( $i = $beg; $i < $end; $i++ )
74     {
75         if ( ( $i % $step ) == 0 )
76         {
77             $txt = "|" . Maasha::Calc::commify( $i );
78             $x   = sprintf( "%.0f", ( ( $i - $beg ) * $factor ) + 2 );
79
80             if ( $x > 0 and $x + ( $cookie->{ 'RULER_FONT_SIZE' } * length $txt ) < $cookie->{ 'IMG_WIDTH' } )
81             {
82                 push @ruler, {
83                     type      => 'text',
84                     txt       => $txt,
85                     font_size => $cookie->{ 'RULER_FONT_SIZE' },
86                     color     => $cookie->{ 'RULER_COLOR' },
87                     x1        => $x,
88                     y1        => $cookie->{ 'TRACK_OFFSET' },
89                 };
90             }
91         }
92     }
93
94     $cookie->{ 'TRACK_OFFSET' } += $cookie->{ 'TRACK_SPACE' };
95
96     return wantarray ? @ruler : \@ruler;
97 }
98
99
100 sub track_seq
101 {
102     # Martin A. Hansen, November 2009.
103  
104     # Create a sequence track by extracting the appropriate
105     # stretch of sequence from the sequence file.
106
107     my ( $cookie,   # browser cookie
108        ) = @_;
109
110     # Returns a list.
111
112     my ( $file, $fh, $seq, @chars, $factor, $i, @seq_list );
113
114     if ( $cookie->{ 'NAV_END' } - $cookie->{ 'NAV_START' } + 1 <= 220 )
115     {
116         $file = path_seq( $cookie );
117         $fh   = Maasha::Filesys::file_read_open( $file );
118         $seq  = Maasha::Filesys::file_read( $fh, $cookie->{ 'NAV_START' } - 1, $cookie->{ 'NAV_END' } - $cookie->{ 'NAV_START' } + 1 );
119         close $fh;
120
121         @chars = split //, $seq;
122
123         $factor = $cookie->{ 'IMG_WIDTH' } / @chars;
124
125         for ( $i = 0; $i < @chars; $i++ ) {
126             push @seq_list, {
127                 type      => 'text',
128                 txt       => $chars[ $i ],
129                 font_size => $cookie->{ 'SEQ_FONT_SIZE' },
130                 color     => $cookie->{ 'SEQ_COLOR' },
131                 x1        => sprintf( "%.0f", $i * $factor ),
132                 y1        => $cookie->{ 'TRACK_OFFSET' },
133             };
134         }
135
136         $cookie->{ 'TRACK_OFFSET' } += $cookie->{ 'TRACK_SPACE' };
137
138         return wantarray ? @seq_list : \@seq_list;
139     }
140     else
141     {
142         return;
143     }
144 }
145
146
147 sub track_feature
148 {
149     # Martin A. Hansen, November 2009.
150
151     # Create a track with features. If there are more than $cookie->FEAT_MAX 
152     # features the track created will be a histogram, else linear.
153
154     my ( $track,    # path to kiss file with track data
155          $cookie,   # cookie hash
156        ) = @_;
157
158     # Returns a list.
159
160     my ( $index, $count, $track_name, $start, $end, $entries, $features );
161
162     $start = $cookie->{ 'NAV_START' };
163     $end   = $cookie->{ 'NAV_END' };
164
165     $index = Maasha::KISS::kiss_index_retrieve( "$track/track_data.kiss.index" );
166     $count = Maasha::KISS::kiss_index_count( $index, $start, $end );
167
168     $track_name = ( split "/", $track )[ -1 ];
169     $track_name =~ s/^\d+_//;
170     $track_name =~ s/_/ /g;
171
172     $features = [ {
173         type      => 'text',
174         txt       => $track_name,
175         font_size => $cookie->{ 'SEQ_FONT_SIZE' },
176         color     => $cookie->{ 'SEQ_COLOR' },
177         x1        => 0,
178         y1        => $cookie->{ 'TRACK_OFFSET' },
179     } ];
180
181     $cookie->{ 'TRACK_OFFSET' } += 10;
182
183     if ( $count > $cookie->{ 'FEAT_MAX' } )
184     {
185         $entries  = Maasha::KISS::kiss_index_get_blocks( $index, $start, $end );
186         push @{ $features }, track_feature_histogram( $cookie, $start, $end, $entries );
187     }  
188     else
189     {
190         $entries  = Maasha::KISS::kiss_index_get_entries( "$track/track_data.kiss", $index, $start, $end );
191         push @{ $features }, track_feature_linear( $cookie, $start, $end, $entries );
192     }  
193
194     return wantarray ? @{ $features } : $features;
195 }
196
197
198 sub track_feature_linear
199 {
200     # Martin A. Hansen, November 2009.
201
202     # Create a linear feature track where the granularity depends
203     # on the lenght of the features and the browser window width.
204
205     my ( $cookie,    # hashref with image draw metrics
206          $beg,       # base window beg
207          $end,       # base window end
208          $entries,   # list of unsorted KISS entries 
209        ) = @_;
210
211     # Returns a list.
212     
213     my ( $factor, $entry, $y_step, @ladder, $y_max, $w, $x1, $y1, $x2, $y2, @features );
214
215     @{ $entries } = sort { $a->{ 'S_BEG' } <=> $b->{ 'S_BEG' } or $a->{ 'S_END' } <=> $b->{ 'S_END' } } @{ $entries };
216
217     $factor = $cookie->{ 'IMG_WIDTH' } / ( $end - $beg + 1 );
218     $y_step = 0;
219     $y_max  = 0;
220
221     foreach $entry ( @{ $entries } )
222     {
223         $w = sprintf( "%.0f", ( $entry->{ 'S_END' } - $entry->{ 'S_BEG' } + 1 ) * $factor );
224
225         if ( $w >= 1 )
226         {
227             $x1 = sprintf( "%.0f", ( $entry->{ 'S_BEG' } - $beg ) * $factor );
228
229             for ( $y_step = 0; $y_step < @ladder; $y_step++ ) {
230                 last if $x1 >= $ladder[ $y_step ] + 1; 
231             }
232
233             $y1 = $cookie->{ 'TRACK_OFFSET' } + ( ( 0.1 + $cookie->{ 'FEAT_WIDTH' } ) * $y_step );
234
235             push @features, {
236                 type       => 'rect',
237                 line_width => $cookie->{ 'FEAT_WIDTH' },
238                 color      => $cookie->{ 'FEAT_COLOR' },
239                 title      => "Q_ID: $entry->{ 'Q_ID' } S_BEG: $entry->{ 'S_BEG' } S_END: $entry->{ 'S_END' } STRAND: $entry->{ 'STRAND' }",
240                 q_id       => $entry->{ 'Q_ID' },
241                 s_beg      => $entry->{ 'S_BEG' },
242                 s_end      => $entry->{ 'S_END' },
243                 strand     => $entry->{ 'STRAND' },
244                 x1         => $x1,
245                 y1         => $y1,
246                 x2         => $x1 + $w,
247                 y2         => $y1 + $cookie->{ 'FEAT_WIDTH' },
248             };
249
250             $y_max = Maasha::Calc::max( $y_max, $y_step * ( 0.1 + $cookie->{ 'FEAT_WIDTH' } ) );
251
252             push @features, feature_align( $entry, $beg, $y1, $factor, $cookie->{ 'FEAT_WIDTH' } ) if $entry->{ 'ALIGN' } ne '.';
253
254             $ladder[ $y_step ] = $x1 + $w;
255         }
256     }
257
258     $cookie->{ 'TRACK_OFFSET' } += $y_max + $cookie->{ 'TRACK_SPACE' };
259
260     return wantarray ? @features : \@features;
261 }
262
263
264 sub feature_align
265 {
266     # Martin A. Hansen, November 2009.
267     
268     # Add to feature track alignment info if the granularity is
269     # sufficient.
270     # TODO: The printing of chars is imprecise.
271
272     my ( $entry,         # Partial KISS entry
273          $beg,           # base window beg
274          $y_offset,      # y axis draw offset
275          $factor,        # scale factor
276          $feat_height,   # hight of feature in pixels
277        ) = @_;
278
279     # Returns a list.
280
281     my ( $w, $align, $pos, $nt_before, $nt_after, $x1, @features );
282
283     $w = sprintf( "%.0f", 1 * $factor );
284
285     if ( $w >= 1 )
286     {
287         foreach $align ( split /,/, $entry->{ 'ALIGN' } )
288         {
289             if ( $align =~ /(\d+):([ATCGN-])>([ATCGN-])/ )
290             {
291                 $pos       = $1;
292                 $nt_before = $2;
293                 $nt_after  = $3;
294             }
295             else
296             {
297                 Maasha::Common::error( qq(BAD align descriptor: "$align") );
298             }
299
300             $x1 = sprintf( "%.0f", ( $entry->{ 'S_BEG' } + $pos - $beg ) * $factor );
301
302             push @features, {
303                 type       => 'rect',
304                 line_width => $feat_height,
305                 color      => [ 1, 0, 0 ],
306                 title      => $align,
307                 x1         => $x1,
308                 y1         => $y_offset,
309                 x2         => $x1 + $w,
310                 y2         => $y_offset + $feat_height,
311             };
312
313             if ( $w > $feat_height )
314             {
315                 push @features, {
316                     type       => 'text',
317                     font_size  => $feat_height + 2,
318                     color      => [ 0, 0, 0 ],
319                     txt        => $nt_after,
320                     x1         => $x1 + sprintf( "%.0f", ( $w / 2 ) ) - $feat_height / 2,
321                     y1         => $y_offset + $feat_height,
322                 };
323             }
324         }
325     }
326
327     return wantarray ? @features : \@features;
328 }
329
330
331 sub track_feature_histogram
332 {
333     # Martin A. Hansen, November 2009.
334     
335     # Create a feature track as a histogram using information
336     # from the index only thus avoiding to load features from the
337     # file.
338
339     my ( $cookie,   # hashref with image draw metrics
340          $min,      # minimum base position
341          $max,      # maximum base position
342          $blocks,   # list of blocks
343        ) = @_;
344
345     # Returns a list.
346
347     my ( $hist_height, $bucket_width, $bucket_count, $min_bucket, $factor, $factor_heigth, $max_height, $block, $bucket_beg, $bucket_end, $i, @buckets, $h, $x, @hist );
348
349     return if $max <= $min;
350
351     $hist_height  = 100;   # pixels
352     $bucket_width = 5;
353     $bucket_count = $cookie->{ 'IMG_WIDTH' } / $bucket_width;
354     $factor       = ( $cookie->{ 'IMG_WIDTH' } / $bucket_width ) / ( $max - $min + 1 );
355
356     $min_bucket = 999999999;
357     $max_height = 0;
358
359     foreach $block ( @{ $blocks } )
360     {
361         $bucket_beg = int( $block->{ 'BEG' } * $factor );
362         $bucket_end = int( $block->{ 'END' } * $factor );
363
364         $min_bucket = Maasha::Calc::min( $min_bucket, $bucket_beg );
365
366         for ( $i = $bucket_beg; $i <= $bucket_end; $i++ )
367         {
368             $buckets[ $i ] += $block->{ 'COUNT' };
369
370             $max_height = Maasha::Calc::max( $max_height, $buckets[ $i ] );
371         }
372     }
373
374     if ( $max_height > 0 )
375     {
376         $factor_heigth = $hist_height / $max_height;
377
378         $x = 0;
379
380         for ( $i = $min_bucket; $i < @buckets; $i++ )
381         {
382             if ( defined $buckets[ $i ] )
383             {
384                 $h = sprintf( "%.0f", $buckets[ $i ] * $factor_heigth );
385
386                 if ( $h >= 1 )
387                 {
388                     push @hist, {
389                         type       => 'line',
390                         line_width => $bucket_width,
391                         color      => $cookie->{ 'FEAT_COLOR' },
392                         title      => "Features: $buckets[ $i ]",
393                         x1         => $x,
394                         y1         => $cookie->{ 'TRACK_OFFSET' } + $hist_height,
395                         x2         => $x,
396                         y2         => $cookie->{ 'TRACK_OFFSET' } + $hist_height - $h,
397                     };
398                 }
399             }
400
401             $x += $bucket_width;
402         }
403     }
404
405     $cookie->{ 'TRACK_OFFSET' } += $hist_height + $cookie->{ 'TRACK_SPACE' };
406
407     return wantarray ? @hist : \@hist;
408 }
409
410
411 sub dna_get
412 {
413     # Martin A. Hansen, November 2009.
414
415     # Returns the sequence from the contig in the beg/end interval
416     # contained in the cookie.
417
418     my ( $cookie,   # cookie hash
419        ) = @_;
420
421     # Returns a string.
422
423     my ( $path, $fh, $beg, $end, $len, $dna );
424
425     $path = path_seq( $cookie );
426
427     $beg = $cookie->{ 'S_BEG' };
428     $end = $cookie->{ 'S_END' };
429     $beg =~ tr/,//d;
430     $end =~ tr/,//d;
431     $len = $end - $beg + 1;
432
433
434     $fh = Maasha::Filesys::file_read_open( $path );
435
436     $dna = Maasha::Filesys::file_read( $fh, $beg, $len );
437
438     $dna = Maasha::Seq::dna_revcomp( $dna ) if $cookie->{ 'STRAND' } eq '-';
439     
440     Maasha::Seq::wrap( \$dna, 100 );
441
442     close $fh;
443
444     return $dna;
445 }
446
447
448 sub path_seq
449 {
450     # Martin A. Hansen, November 2009.
451
452     # Returns the path to the sequence file for a specified
453     # contig as written in the cookie.
454
455     my ( $cookie,   # cookie hash
456        ) = @_;
457
458     # Returns a string.
459
460     my ( $path );
461
462     die qq(ERROR: no USER in cookie.\n)     if not $cookie->{ 'USER' };
463     die qq(ERROR: no CLADE in cookie.\n)    if not $cookie->{ 'CLADE' };
464     die qq(ERROR: no GENOME in cookie.\n)   if not $cookie->{ 'GENOME' };
465     die qq(ERROR: no ASSEMBLY in cookie.\n) if not $cookie->{ 'ASSEMBLY' };
466     die qq(ERROR: no CONTIG in cookie.\n)   if not $cookie->{ 'CONTIG' };
467
468     $path = join( "/",
469         $cookie->{ 'DATA_DIR' },
470         "Users",
471         $cookie->{ 'USER' },
472         $cookie->{ 'CLADE' },
473         $cookie->{ 'GENOME' },
474         $cookie->{ 'ASSEMBLY' },
475         $cookie->{ 'CONTIG' },
476         "Sequence",
477         "sequence.txt"
478     );
479     
480     die qq(ERROR: no such file: "$path".\n) if not -e $path;
481
482     return $path;
483 }
484
485
486 sub path_tracks
487 {
488     # Martin A. Hansen, November 2009.
489     
490     # Returns a list of paths to all tracks for a specified
491     # contig as written in the cookie.
492
493     my ( $cookie,   # cookie path
494        ) = @_;
495
496     # Returns a list.
497
498     my ( $path, @tracks );
499
500     die qq(ERROR: no USER in cookie.\n)     if not $cookie->{ 'USER' };
501     die qq(ERROR: no CLADE in cookie.\n)    if not $cookie->{ 'CLADE' };
502     die qq(ERROR: no GENOME in cookie.\n)   if not $cookie->{ 'GENOME' };
503     die qq(ERROR: no ASSEMBLY in cookie.\n) if not $cookie->{ 'ASSEMBLY' };
504     die qq(ERROR: no CONTIG in cookie.\n)   if not $cookie->{ 'CONTIG' };
505
506     $path = join( "/",
507         $cookie->{ 'DATA_DIR' },
508         "Users",
509         $cookie->{ 'USER' },
510         $cookie->{ 'CLADE' },
511         $cookie->{ 'GENOME' },
512         $cookie->{ 'ASSEMBLY' },
513         $cookie->{ 'CONTIG' },
514         "Tracks",
515     );
516
517     if ( -d $path )
518     {
519         @tracks = Maasha::Filesys::ls_dirs( $path );
520
521         @tracks = grep { $_ !~ /\/\.\.?$/ } @tracks;
522
523         return wantarray ? @tracks : \@tracks;
524     }
525     else
526     {
527         return wantarray ? () : [];
528     }
529 }
530
531
532 sub search_tracks
533 {
534     # Martin A. Hansen, December 2009.
535
536     # Uses grep to search all tracks in all contigs
537     # for a given pattern and return a list of KISS entries.
538
539     my ( $cookie,   # cookie hash
540        ) = @_;
541
542     # Returns a list.
543
544     my ( $contig, @tracks, $track, $file, $line, $out_file, $fh, $entry, @entries );
545
546     foreach $contig ( @{ $cookie->{ 'LIST_CONTIG' } } )
547     {
548         $cookie->{ 'CONTIG' } = $contig;
549
550         push @tracks, path_tracks( $cookie );
551     }
552
553     foreach $track ( @tracks )
554     {
555         $file = "$track/track_data.kiss";
556       
557         if ( -f $file )
558         {
559             $fh = Maasha::Filesys::file_read_open( $file );
560
561             while ( $line = <$fh> )
562             {
563                 chomp $line;
564
565                 if ( $line =~ /$cookie->{ 'SEARCH' }/i )
566                 {
567                     $entry = Maasha::KISS::kiss_entry_parse( $line );
568
569                     push @entries, $entry;
570                 }
571             }
572
573             close $fh;
574         }
575     }
576
577     return wantarray ? @entries : \@entries;
578 }
579
580
581 # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
582
583 1;
584