]> git.donarmstrong.com Git - biopieces.git/blob - code_perl/Maasha/KISS.pm
d0267675ddae568f772e783af21575a3b75d3211
[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     return $offset;
305 }
306
307
308 sub kiss_index_count
309 {
310     # Martin A. Hansen, November 2009.
311     
312     # Given a KISS index and a begin/end interval
313     # sum the number of counts in that interval,
314     # and return this.
315
316     my ( $index,   # KISS index
317          $beg,     # Begin position
318          $end,     # End position
319          $level,   # index level  - OPTIONAL
320          $factor,  # index factor - OPTIONAL
321        ) = @_;
322
323     # Returns a number.
324     
325     my ( $count, $child );
326
327     $level  ||= INDEX_LEVEL;
328     $factor ||= INDEX_FACTOR;
329     $count  ||= 0;
330
331     foreach $child ( @{ $index->{ 'CHILDREN' } } )
332     {
333         next if not defined $child;
334
335         if ( $level >= $factor )
336         {
337             if ( Maasha::Calc::overlap( $beg, $end, $child->{ 'BEG' }, $child->{ 'END' } ) )
338             {
339                 $count += $child->{ 'COUNT' } if $level == $factor;
340                 $count += kiss_index_count( $child, $beg, $end, $level / $factor, $factor );
341             }
342         }
343     }
344
345     return $count;
346 }
347
348
349 sub kiss_index_store
350 {
351     # Martin A. Hansen, November 2009.
352
353     # Stores a KISS index to a file.
354
355     my ( $path,    # path to KISS index
356          $index,   # KISS index
357        ) = @_;
358
359     # Returns nothing.
360
361     Maasha::Filesys::file_store( $path, $index );
362 }
363
364
365 sub kiss_index_retrieve
366 {
367     # Martin A. Hansen, November 2009.
368
369     # Retrieves a KISS index from a file.
370
371     my ( $path,   # Path to KISS index
372        ) = @_;
373
374     # Returns a data structure.
375
376     my ( $index );
377
378     $index = Maasha::Filesys::file_retrieve( $path );
379
380     return wantarray ? @{ $index } : $index;
381 }
382
383
384 sub kiss_index_get_entries
385 {
386     # Martin A. Hansen, November 2009.
387
388     # Given a path to a KISS file and a KISS index
389     # along with a beg/end interval, locate all entries
390     # in that interval and return those.
391
392     my ( $file,    # path to KISS file
393          $index,   # KISS index
394          $beg,     # interval begin
395          $end,     # interval end
396        ) = @_;
397
398     # Returns a list.
399
400     my ( $offset, $fh, $entry, @entries );
401
402     $offset = kiss_index_offset( $index, $beg );
403
404     $fh = Maasha::Filesys::file_read_open( $file );
405
406     sysseek( $fh, $offset, 0 );
407
408     while ( $entry = kiss_entry_get( $fh ) )
409     {
410         push @entries, $entry if $entry->{ 'S_END' } > $beg;
411
412         last if $entry->{ 'S_BEG' } > $end;
413     }
414
415     close $fh;
416
417     return wantarray ? @entries : \@entries;
418 }
419
420
421 sub kiss_index_get_blocks
422 {
423     # Martin A. Hansen, November 2009.
424
425     # Given a KISS index recursively traverse
426     # this into the appropriate node size determined
427     # by the size of the given beg/end interval.
428     # Blocks consisting of hashes of BEG/END/COUNT 
429     # are returned from the requested node size.
430
431     my ( $index,   # KISS index node
432          $beg,     # interval begin
433          $end,     # interval end
434          $level,   # index level  - OPTIONAL
435          $factor,  # index factor - OPTIONAL
436          $size,    # requested node size
437        ) = @_;
438
439     # Returns a list.
440     
441     my ( $len, @blocks, $child );
442
443     $level  ||= INDEX_LEVEL;
444     $factor ||= INDEX_FACTOR;
445
446     $size ||= 100;   # TODO: lazy list loading?
447
448 #    if ( not defined $size )
449 #    {
450 #        $len = $end - $beg + 1;
451 #    
452 #        if ( $len > 100_000_000 ) {
453 #            $size = 1_000_000;
454 #        } elsif ( $len > 1_000_000 ) {
455 #            $size = 10_000;
456 #        } else {
457 #            $size = 100;
458 #        }
459 #    }
460
461     if ( $level >= $size )
462     {
463         foreach $child ( @{ $index->{ 'CHILDREN' } } )
464         {
465             next if not defined $child;
466
467             if ( Maasha::Calc::overlap( $beg, $end, $child->{ 'BEG' }, $child->{ 'END' } ) )
468             {
469                 if ( $level == $size )
470                 {
471                     push @blocks, {
472                         BEG   => $child->{ 'BEG' },
473                         END   => $child->{ 'END' },
474                         COUNT => $child->{ 'COUNT' },
475                     };
476                 }
477
478                 push @blocks, kiss_index_get_blocks( $child, $beg, $end, $level / $factor, $factor, $size );
479             }
480         }
481     }
482
483     return wantarray ? @blocks : \@blocks;
484 }
485
486
487 sub kiss_align_enc
488 {
489     # Martin A. Hansen, November 2009.
490
491     # Encodes alignment descriptors for two
492     # aligned sequences.
493
494     my ( $s_seq,    # Subject sequence reference
495          $q_seq,    # Query sequence reference
496          $offset,   # alternate offset - OPTIONAL
497        ) = @_;
498
499     # Returns a list
500
501     my ( $i, $s, $q, @align );
502
503     Maasha::Common::error( "Sequence lengths don't match" ) if length ${ $s_seq } != length ${ $q_seq };
504
505     $offset ||= 0;
506
507     for ( $i = 0; $i < length ${ $s_seq }; $i++ )
508     {
509         $s = uc substr ${ $s_seq }, $i, 1;
510         $q = uc substr ${ $q_seq }, $i, 1;
511
512         if ( $s eq '-' and $q eq '-' )
513         {
514             # do nothing
515         }
516         elsif ( $s eq $q )
517         {
518             $offset++;
519         }
520         else
521         {
522             push @align, "$offset:$s>$q";
523
524             $offset++ if not $s eq '-';
525         }
526     }
527
528     return wantarray ? @align : \@align;
529 }   
530
531
532 sub kiss_align_dec
533 {
534     # Martin A. Hansen, November 2009.
535
536     # Test routine of correct resolve of ALIGN descriptors.
537
538     # S.aur_COL       41      75      5_vnlh2ywXsN1/1 1       -       .       17:A>T,31:A>N   .       .       .       .
539
540     my ( $s_seqref,   # subject sequence reference
541          $entry,      # KISS entry
542        ) = @_;
543
544     # Returns a string.
545
546     my ( $align, $pos, $s_symbol, $q_symbol, $s_seq, $q_seq, $insertions );
547
548     $s_seq = substr ${ $s_seqref }, $entry->{ 'S_BEG' }, $entry->{ 'S_END' } - $entry->{ 'S_BEG' } + 1;
549     $q_seq = $s_seq;
550
551     $insertions = 0;
552
553     foreach $align ( split /,/, $entry->{ 'ALIGN' } )
554     {
555         if ( $align =~ /(\d+):(.)>(.)/ )
556         {
557             $pos      = $1;
558             $s_symbol = $2;
559             $q_symbol = $3;
560
561             if ( $s_symbol eq '-' ) # insertion
562             {
563                 substr $s_seq, $pos + $insertions, 0, $s_symbol;
564                 substr $q_seq, $pos + $insertions, 0, $q_symbol;
565
566                 $insertions++;
567             }
568             elsif ( $q_symbol eq '-' ) # deletion
569             {
570                 substr $q_seq, $pos + $insertions, 1, $q_symbol;
571             }
572             else # mismatch
573             {
574                 substr $q_seq, $pos + $insertions, 1, $q_symbol;
575             }
576         }
577         else
578         {
579             Maasha::Common::error( qq(Bad alignment descriptor: "$align") );
580         }
581     }
582
583     Maasha::Align::align_print_pairwise( [ $entry->{ 'S_ID' }, $s_seq ], [ $entry->{ 'Q_ID' }, $q_seq ] );
584 }
585
586
587 sub kiss2biopiece
588 {
589     # Martin A. Hansen, November 2009.
590
591     # Converts a KISS entry to a Biopiece record.
592     # TODO: Consistency checking
593
594     my ( $entry,   # KISS entry
595        ) = @_;
596
597     # Returns a hashref
598
599     return wantarray ? %{ $entry } : $entry;
600 }
601
602
603 sub biopiece2kiss
604 {
605     # Martin A. Hansen, November 2009.
606
607     # Converts a Biopiece record to a KISS entry.
608  
609     my ( $record,   # Biopiece record
610        ) = @_;
611
612     # Returns a hashref
613
614     $record->{ 'SCORE' }       ||= $record->{ 'E_VAL' } || ".";
615     $record->{ 'HITS' }        ||= ".";
616     $record->{ 'BLOCK_COUNT' } ||= ".";
617     $record->{ 'BLOCK_BEGS' }  ||= ".";
618     $record->{ 'BLOCK_LENS' }  ||= ".";
619     $record->{ 'BLOCK_TYPE' }  ||= ".";
620     $record->{ 'ALIGN' }       ||= $record->{ 'DESCRIPTOR' } || ".";
621
622     return wantarray ? %{ $record } : $record;
623 }
624
625
626 # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
627
628 1;
629