]> git.donarmstrong.com Git - biopieces.git/blob - code_perl/Maasha/KISS.pm
added file_md5 routine to Filesys.pm
[biopieces.git] / code_perl / Maasha / KISS.pm
1 package Maasha::KISS;
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 parsing and emitting KISS records.
26
27 # http://code.google.com/p/biopieces/wiki/KissFormat
28
29
30 # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
31
32
33 use warnings;
34 use strict;
35 use Data::Dumper;
36 use Maasha::Common;
37 use Maasha::Filesys;
38 use Maasha::Align;
39 use vars qw( @ISA @EXPORT );
40
41 @ISA = qw( Exporter );
42
43 use constant {
44     S_ID             => 0,
45     S_BEG            => 1,
46     S_END            => 2,
47     Q_ID             => 3,
48     SCORE            => 4,
49     STRAND           => 5,
50     HITS             => 6,
51     ALIGN            => 7,
52     BLOCK_COUNT      => 8,
53     BLOCK_BEGS       => 9,
54     BLOCK_LENS       => 10,
55     BLOCK_TYPE       => 11,
56     INDEX_BLOCK_SIZE => 100,
57     INDEX_LEVEL      => 100_000_000,
58     INDEX_FACTOR     => 100,
59 };
60
61
62 # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
63
64
65 sub kiss_entry_get
66 {
67     # Martin A. Hansen, November 2009.
68
69     # Gets the next KISS entry from a file handle.
70
71     my ( $fh,    # file handle
72        ) = @_;
73
74     # Returns a hashref.
75
76     my ( $line, @fields, %entry );
77
78     while ( $line = <$fh> )
79     {
80         chomp $line;
81
82         next if $line =~ /^$|^#/;
83
84         @fields = split /\t/, $line;
85
86         Maasha::Common::error( qq(BAD kiss entry: $line) ) if not @fields == 12;
87         
88         $entry{ 'S_ID' }        = $fields[ S_ID ];
89         $entry{ 'S_BEG' }       = $fields[ S_BEG ];
90         $entry{ 'S_END' }       = $fields[ S_END ];
91         $entry{ 'Q_ID' }        = $fields[ Q_ID ];
92         $entry{ 'SCORE' }       = $fields[ SCORE ];
93         $entry{ 'STRAND' }      = $fields[ STRAND ];
94         $entry{ 'HITS' }        = $fields[ HITS ];
95         $entry{ 'ALIGN' }       = $fields[ ALIGN ];
96         $entry{ 'BLOCK_COUNT' } = $fields[ BLOCK_COUNT ];
97         $entry{ 'BLOCK_BEGS' }  = $fields[ BLOCK_BEGS ];
98         $entry{ 'BLOCK_LENS' }  = $fields[ BLOCK_LENS ];
99         $entry{ 'BLOCK_TYPE' }  = $fields[ BLOCK_TYPE ];
100
101         return wantarray ? %entry : \%entry;
102     }
103 }
104
105
106 sub kiss_entry_parse
107 {
108     # Martin A. Hansen, December 2009.
109
110     # Parses a line with a KISS entry.
111
112     my ( $line,   #  KISS line to parse
113        ) = @_;
114
115     # Returns a hash
116
117     my ( @fields, %entry );
118
119     @fields = split /\t/, $line;
120
121     Maasha::Common::error( qq(BAD kiss entry: $line) ) if not @fields == 12;
122     
123     $entry{ 'S_ID' }        = $fields[ S_ID ];
124     $entry{ 'S_BEG' }       = $fields[ S_BEG ];
125     $entry{ 'S_END' }       = $fields[ S_END ];
126     $entry{ 'Q_ID' }        = $fields[ Q_ID ];
127     $entry{ 'SCORE' }       = $fields[ SCORE ];
128     $entry{ 'STRAND' }      = $fields[ STRAND ];
129     $entry{ 'HITS' }        = $fields[ HITS ];
130     $entry{ 'ALIGN' }       = $fields[ ALIGN ];
131     $entry{ 'BLOCK_COUNT' } = $fields[ BLOCK_COUNT ];
132     $entry{ 'BLOCK_BEGS' }  = $fields[ BLOCK_BEGS ];
133     $entry{ 'BLOCK_LENS' }  = $fields[ BLOCK_LENS ];
134     $entry{ 'BLOCK_TYPE' }  = $fields[ BLOCK_TYPE ];
135
136     return wantarray ? %entry : \%entry;
137 }
138
139
140 sub kiss_entry_put
141 {
142     # Martin A. Hansen, November 2009.
143
144     # Outputs a KISS record to a given filehandle
145     # or STDOUT if no filehandle is given.
146
147     my ( $entry,   # KISS entry to output
148          $fh,      # file handle  -  OPTIONAL
149        ) = @_;
150
151     # Returns nothing.
152     
153     my ( @fields );
154
155     if ( defined $entry->{ 'S_ID' }  and 
156          defined $entry->{ 'S_BEG' } and
157          defined $entry->{ 'S_END' }
158        )
159     {
160         Maasha::Common::error( qq(Bad S_BEG value: $entry->{ 'S_BEG' } < 0 ) ) if $entry->{ 'S_BEG' } < 0;
161         Maasha::Common::error( qq(Bad S_END value: $entry->{ 'S_END' } < $entry->{ 'S_BEG' }) ) if $entry->{ 'S_END' } < $entry->{ 'S_BEG' };
162
163         $fh ||= \*STDOUT;
164     
165         $fields[ S_ID ]        = $entry->{ 'S_ID' };
166         $fields[ S_BEG ]       = $entry->{ 'S_BEG' };
167         $fields[ S_END ]       = $entry->{ 'S_END' };
168         $fields[ Q_ID ]        = $entry->{ 'Q_ID' }        || ".";
169         $fields[ SCORE ]       = $entry->{ 'SCORE' }       || ".";
170         $fields[ STRAND ]      = $entry->{ 'STRAND' }      || ".";
171         $fields[ HITS ]        = $entry->{ 'HITS' }        || ".";
172         $fields[ ALIGN ]       = $entry->{ 'ALIGN' }       || ".";
173         $fields[ BLOCK_COUNT ] = $entry->{ 'BLOCK_COUNT' } || ".";
174         $fields[ BLOCK_BEGS ]  = $entry->{ 'BLOCK_BEGS' }  || ".";
175         $fields[ BLOCK_LENS ]  = $entry->{ 'BLOCK_LENS' }  || ".";
176         $fields[ BLOCK_TYPE ]  = $entry->{ 'BLOCK_TYPE' }  || ".";
177
178         print $fh join( "\t", @fields ), "\n";
179     }
180 }
181
182
183 sub kiss_sort
184 {
185     # Martin A. Hansen, November 2009.
186
187     # Sorts a KISS file on S_BEG and S_END
188
189     my ( $file,   # KISS file
190        ) = @_;
191
192     # Returns nothing.
193
194     `sort -k 2,2n -k 3,3n $file > $file.sort`;
195
196     rename "$file.sort", $file;
197 }
198
199
200 sub kiss_index
201 {
202     # Martin A, Hansen, November 2009.
203
204     # Creates an index of a sorted KISS file.
205
206     my ( $file,   # KISS file
207        ) = @_;
208
209     # Returns a hashref.
210
211     my ( $tree, $offset, $fh, $line, $beg );
212
213     $tree   = {};
214     $offset = 0;
215
216     $fh = Maasha::Filesys::file_read_open( $file );
217
218     while ( $line = <$fh> )
219     {
220         ( undef, $beg ) = split "\t", $line, 3;
221
222         kiss_index_node_add( $tree, INDEX_LEVEL, INDEX_FACTOR, $beg, $offset );
223                 
224         $offset += length $line;
225     }
226
227     close $fh;
228
229     kiss_index_store( "$file.index", $tree );
230 }
231
232
233 sub kiss_index_node_add
234 {
235     # Martin A, Hansen, November 2009.
236
237     # Recursive routine to add nodes to a tree.
238
239     my ( $node,
240          $level,
241          $factor,
242          $beg,
243          $offset,
244          $sum,
245        ) = @_;
246        
247     my ( $bucket );
248     
249     $sum  ||= 0;
250     $bucket = int( $beg / $level );
251     
252     if ( $level >= $factor )
253     {
254         $sum += $bucket * $level;
255         $beg -= $bucket * $level;
256         
257         $node->{ 'CHILDREN' }->[ $bucket ]->{ 'COUNT' }++; 
258         # $node->{ 'CHILDREN' }->[ $bucket ]->{ 'LEVEL' }  = $level;
259         # $node->{ 'CHILDREN' }->[ $bucket ]->{ 'BUCKET' } = $bucket;
260         $node->{ 'CHILDREN' }->[ $bucket ]->{ 'BEG' }    = $sum;
261         $node->{ 'CHILDREN' }->[ $bucket ]->{ 'END' }    = $sum + $level - 1;
262         $node->{ 'CHILDREN' }->[ $bucket ]->{ 'OFFSET' } = $offset if not defined $node->{ 'CHILDREN' }->[ $bucket ]->{ 'OFFSET' };
263         
264         kiss_index_node_add( $node->{ 'CHILDREN' }->[ $bucket ], $level / $factor, $factor, $beg, $offset, $sum );
265     }   
266 }
267
268
269 sub kiss_index_offset
270 {
271     # Martin A. Hansen, November 2009.
272
273     # Given a KISS index and a begin position,
274     # locate the offset closest to the begin position,
275     # and return this.
276     
277     my ( $index,    # KISS index
278          $beg,      # begin position
279          $level,    # index level  - OPTIONAL
280          $factor,   # index factor - OPTIONAL
281        ) = @_;
282
283     # Returns a number.
284
285     my ( $child, $offset );
286
287     $level  ||= INDEX_LEVEL;
288     $factor ||= INDEX_FACTOR;
289
290     foreach $child ( @{ $index->{ 'CHILDREN' } } )
291     {
292         next if not defined $child;
293
294         if ( $child->{ 'BEG' } <= $beg and $beg <= $child->{ 'END' } )
295         {
296             if ( $level == $factor ) {
297                 $offset = $child->{ 'OFFSET' };
298             } else {
299                 $offset = kiss_index_offset( $child, $beg, $level / $factor, $factor );
300             }
301         }
302     }
303
304     # Maasha::Common::error( "No offset" ) if not defined $offset;   # FIXME
305
306     return $offset;
307 }
308
309
310 sub kiss_index_count
311 {
312     # Martin A. Hansen, November 2009.
313     
314     # Given a KISS index and a begin/end interval
315     # sum the number of counts in that interval,
316     # and return this.
317
318     my ( $index,   # KISS index
319          $beg,     # Begin position
320          $end,     # End position
321          $level,   # index level  - OPTIONAL
322          $factor,  # index factor - OPTIONAL
323        ) = @_;
324
325     # Returns a number.
326     
327     my ( $count, $child );
328
329     $level  ||= INDEX_LEVEL;
330     $factor ||= INDEX_FACTOR;
331     $count  ||= 0;
332
333     foreach $child ( @{ $index->{ 'CHILDREN' } } )
334     {
335         next if not defined $child;
336
337         if ( $level >= $factor )
338         {
339             if ( Maasha::Calc::overlap( $beg, $end, $child->{ 'BEG' }, $child->{ 'END' } ) )
340             {
341                 $count += $child->{ 'COUNT' } if $level == $factor;
342                 $count += kiss_index_count( $child, $beg, $end, $level / $factor, $factor );
343             }
344         }
345     }
346
347     return $count;
348 }
349
350
351 sub kiss_index_store
352 {
353     # Martin A. Hansen, November 2009.
354
355     # Stores a KISS index to a file.
356
357     my ( $path,    # path to KISS index
358          $index,   # KISS index
359        ) = @_;
360
361     # Returns nothing.
362
363     Maasha::Filesys::file_store( $path, $index );
364 }
365
366
367 sub kiss_index_retrieve
368 {
369     # Martin A. Hansen, November 2009.
370
371     # Retrieves a KISS index from a file.
372
373     my ( $path,   # Path to KISS index
374        ) = @_;
375
376     # Returns a data structure.
377
378     my ( $index );
379
380     $index = Maasha::Filesys::file_retrieve( $path );
381
382     return wantarray ? @{ $index } : $index;
383 }
384
385
386 sub kiss_index_get_entries
387 {
388     # Martin A. Hansen, November 2009.
389
390     # Given a path to a KISS file and a KISS index
391     # along with a beg/end interval, locate all entries
392     # in that interval and return those.
393
394     my ( $file,    # path to KISS file
395          $index,   # KISS index
396          $beg,     # interval begin
397          $end,     # interval end
398        ) = @_;
399
400     # Returns a list.
401
402     my ( $offset, $fh, $entry, @entries );
403
404     $offset = kiss_index_offset( $index, $beg );
405
406     $fh = Maasha::Filesys::file_read_open( $file );
407
408     sysseek( $fh, $offset, 0 );
409
410     while ( $entry = kiss_entry_get( $fh ) )
411     {
412         push @entries, $entry if $entry->{ 'S_END' } > $beg;
413
414         last if $entry->{ 'S_BEG' } > $end;
415     }
416
417     close $fh;
418
419     return wantarray ? @entries : \@entries;
420 }
421
422
423 sub kiss_index_get_blocks
424 {
425     # Martin A. Hansen, November 2009.
426
427     # Given a KISS index recursively traverse
428     # this into the appropriate node size determined
429     # by the size of the given beg/end interval.
430     # Blocks consisting of hashes of BEG/END/COUNT 
431     # are returned from the requested node size.
432
433     my ( $index,   # KISS index node
434          $beg,     # interval begin
435          $end,     # interval end
436          $level,   # index level  - OPTIONAL
437          $factor,  # index factor - OPTIONAL
438          $size,    # requested node size
439        ) = @_;
440
441     # Returns a list.
442     
443     my ( $len, @blocks, $child );
444
445     $level  ||= INDEX_LEVEL;
446     $factor ||= INDEX_FACTOR;
447
448     $size ||= 100;   # TODO: lazy list loading?
449
450 #    if ( not defined $size )
451 #    {
452 #        $len = $end - $beg + 1;
453 #    
454 #        if ( $len > 100_000_000 ) {
455 #            $size = 1_000_000;
456 #        } elsif ( $len > 1_000_000 ) {
457 #            $size = 10_000;
458 #        } else {
459 #            $size = 100;
460 #        }
461 #    }
462
463     if ( $level >= $size )
464     {
465         foreach $child ( @{ $index->{ 'CHILDREN' } } )
466         {
467             next if not defined $child;
468
469             if ( Maasha::Calc::overlap( $beg, $end, $child->{ 'BEG' }, $child->{ 'END' } ) )
470             {
471                 if ( $level == $size )
472                 {
473                     push @blocks, {
474                         BEG   => $child->{ 'BEG' },
475                         END   => $child->{ 'END' },
476                         COUNT => $child->{ 'COUNT' },
477                     };
478                 }
479
480                 push @blocks, kiss_index_get_blocks( $child, $beg, $end, $level / $factor, $factor, $size );
481             }
482         }
483     }
484
485     return wantarray ? @blocks : \@blocks;
486 }
487
488
489 sub kiss_align_enc
490 {
491     # Martin A. Hansen, November 2009.
492
493     # Encodes alignment descriptors for two
494     # aligned sequences.
495
496     my ( $s_seq,    # Subject sequence reference
497          $q_seq,    # Query sequence reference
498          $offset,   # alternate offset - OPTIONAL
499        ) = @_;
500
501     # Returns a list
502
503     my ( $i, $s, $q, @align );
504
505     Maasha::Common::error( "Sequence lengths don't match" ) if length ${ $s_seq } != length ${ $q_seq };
506
507     $offset ||= 0;
508
509     for ( $i = 0; $i < length ${ $s_seq }; $i++ )
510     {
511         $s = uc substr ${ $s_seq }, $i, 1;
512         $q = uc substr ${ $q_seq }, $i, 1;
513
514         if ( $s eq '-' and $q eq '-' )
515         {
516             # do nothing
517         }
518         elsif ( $s eq $q )
519         {
520             $offset++;
521         }
522         else
523         {
524             push @align, "$offset:$s>$q";
525
526             $offset++ if not $s eq '-';
527         }
528     }
529
530     return wantarray ? @align : \@align;
531 }   
532
533
534 sub kiss_align_dec
535 {
536     # Martin A. Hansen, November 2009.
537
538     # Test routine of correct resolve of ALIGN descriptors.
539
540     # S.aur_COL       41      75      5_vnlh2ywXsN1/1 1       -       .       17:A>T,31:A>N   .       .       .       .
541
542     my ( $s_seqref,   # subject sequence reference
543          $entry,      # KISS entry
544        ) = @_;
545
546     # Returns a string.
547
548     my ( $align, $pos, $s_symbol, $q_symbol, $s_seq, $q_seq, $insertions );
549
550     $s_seq = substr ${ $s_seqref }, $entry->{ 'S_BEG' }, $entry->{ 'S_END' } - $entry->{ 'S_BEG' } + 1;
551     $q_seq = $s_seq;
552
553     $insertions = 0;
554
555     foreach $align ( split /,/, $entry->{ 'ALIGN' } )
556     {
557         if ( $align =~ /(\d+):(.)>(.)/ )
558         {
559             $pos      = $1;
560             $s_symbol = $2;
561             $q_symbol = $3;
562
563             if ( $s_symbol eq '-' ) # insertion
564             {
565                 substr $s_seq, $pos + $insertions, 0, $s_symbol;
566                 substr $q_seq, $pos + $insertions, 0, $q_symbol;
567
568                 $insertions++;
569             }
570             elsif ( $q_symbol eq '-' ) # deletion
571             {
572                 substr $q_seq, $pos + $insertions, 1, $q_symbol;
573             }
574             else # mismatch
575             {
576                 substr $q_seq, $pos + $insertions, 1, $q_symbol;
577             }
578         }
579         else
580         {
581             Maasha::Common::error( qq(Bad alignment descriptor: "$align") );
582         }
583     }
584
585     Maasha::Align::align_print_pairwise( [ $entry->{ 'S_ID' }, $s_seq ], [ $entry->{ 'Q_ID' }, $q_seq ] );
586 }
587
588
589 sub kiss2biopiece
590 {
591     # Martin A. Hansen, November 2009.
592
593     # Converts a KISS entry to a Biopiece record.
594     # TODO: Consistency checking
595
596     my ( $entry,   # KISS entry
597        ) = @_;
598
599     # Returns a hashref
600
601     return wantarray ? %{ $entry } : $entry;
602 }
603
604
605 sub biopiece2kiss
606 {
607     # Martin A. Hansen, November 2009.
608
609     # Converts a Biopiece record to a KISS entry.
610  
611     my ( $record,   # Biopiece record
612        ) = @_;
613
614     # Returns a hashref
615
616     $record->{ 'SCORE' }       ||= $record->{ 'E_VAL' } || ".";
617     $record->{ 'HITS' }        ||= ".";
618     $record->{ 'BLOCK_COUNT' } ||= ".";
619     $record->{ 'BLOCK_BEGS' }  ||= ".";
620     $record->{ 'BLOCK_LENS' }  ||= ".";
621     $record->{ 'BLOCK_TYPE' }  ||= ".";
622     $record->{ 'ALIGN' }       ||= $record->{ 'DESCRIPTOR' } || ".";
623
624     return wantarray ? %{ $record } : $record;
625 }
626
627
628 # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
629
630 1;
631