]> git.donarmstrong.com Git - biopieces.git/blob - code_perl/Maasha/KISS.pm
moving back to binned index and using JSON for storing
[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 JSON::XS;
37 use Maasha::Common;
38 use Maasha::Filesys;
39 use Maasha::Align;
40
41 use vars qw( @ISA @EXPORT );
42
43 @ISA = qw( Exporter );
44
45 use constant {
46     S_ID             => 0,
47     S_BEG            => 1,
48     S_END            => 2,
49     Q_ID             => 3,
50     SCORE            => 4,
51     STRAND           => 5,
52     HITS             => 6,
53     ALIGN            => 7,
54     BLOCK_COUNT      => 8,
55     BLOCK_BEGS       => 9,
56     BLOCK_LENS       => 10,
57     BLOCK_TYPE       => 11,
58     INDEX_BLOCK_SIZE => 100,
59     INDEX_LEVEL      => 100_000_000,
60     INDEX_FACTOR     => 100,
61
62     BUCKET_SIZE      => 100,
63     COUNT            => 0,
64     OFFSET           => 1,
65
66     INDEX_BEG        => 1,
67     INDEX_END        => 2,
68     INDEX            => 12,
69 };
70
71
72 # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
73
74
75 sub kiss_entry_get
76 {
77     # Martin A. Hansen, November 2009.
78
79     # Gets the next KISS entry from a file handle.
80
81     my ( $fh,    # file handle
82        ) = @_;
83
84     # Returns a hashref.
85
86     my ( $line, @fields );
87
88     while ( $line = <$fh> )
89     {
90         chomp $line;
91
92         next if $line =~ /^$|^#/;
93
94         @fields = split /\t/, $line;
95
96         Maasha::Common::error( qq(BAD kiss entry: $line) ) if not @fields == 12;
97         
98         return wantarray ? @fields : \@fields;
99     }
100 }
101
102
103 sub kiss_entry_parse
104 {
105     # Martin A. Hansen, December 2009.
106
107     # Parses a line with a KISS entry.
108
109     my ( $line,   #  KISS line to parse
110        ) = @_;
111
112     # Returns a hash
113
114     my ( @fields, %entry );
115
116     @fields = split /\t/, $line;
117
118     Maasha::Common::error( qq(BAD kiss entry: $line) ) if not @fields == 12;
119     
120     return wantarray ? @fields : \@fields;
121 }
122
123
124 sub kiss_entry_put
125 {
126     # Martin A. Hansen, November 2009.
127
128     # Outputs a KISS record to a given filehandle
129     # or STDOUT if no filehandle is given.
130
131     my ( $entry,   # KISS entry to output
132          $fh,      # file handle  -  OPTIONAL
133        ) = @_;
134
135     # Returns nothing.
136     
137     Maasha::Common::error( qq(BAD kiss entry) ) if not scalar @{ $entry } == 12;
138
139     if ( defined $entry->[ S_ID ]  and 
140          defined $entry->[ S_BEG ] and
141          defined $entry->[ S_END ]
142        )
143     {
144         Maasha::Common::error( qq(Bad S_BEG value: $entry->[ S_BEG ] < 0 ) ) if $entry->[ S_BEG ] < 0;
145         Maasha::Common::error( qq(Bad S_END value: $entry->[ S_END ] < $entry->[ S_BEG ] ) ) if $entry->[ S_END ] < $entry->[ S_BEG ];
146
147         $fh ||= \*STDOUT;
148     
149         $entry->[ Q_ID ]        = "." if not defined $entry->[ Q_ID ];
150         $entry->[ SCORE ]       = "." if not defined $entry->[ SCORE ];
151         $entry->[ STRAND ]      = "." if not defined $entry->[ STRAND ];
152         $entry->[ HITS ]        = "." if not defined $entry->[ HITS ];
153         $entry->[ ALIGN ]       = "." if not defined $entry->[ ALIGN ];
154         $entry->[ BLOCK_COUNT ] = "." if not defined $entry->[ BLOCK_COUNT ];
155         $entry->[ BLOCK_BEGS ]  = "." if not defined $entry->[ BLOCK_BEGS ];
156         $entry->[ BLOCK_LENS ]  = "." if not defined $entry->[ BLOCK_LENS ];
157         $entry->[ BLOCK_TYPE ]  = "." if not defined $entry->[ BLOCK_TYPE ];
158
159         print $fh join( "\t", @{ $entry } ), "\n";
160     }
161 }
162
163
164 sub kiss_sort
165 {
166     # Martin A. Hansen, November 2009.
167
168     # Sorts a KISS file on S_BEG and S_END
169
170     my ( $file,   # KISS file
171        ) = @_;
172
173     # Returns nothing.
174
175     `sort -k 2,2n -k 3,3nr $file > $file.sort`;
176
177     rename "$file.sort", $file;
178 }
179
180
181 sub kiss_index
182 {
183     # Martin A. Hansen, December 2009.
184
185     # Creates a lookup index of a sorted KISS file.
186
187     my ( $file,   # path to KISS file
188        ) = @_;
189
190     # Returns nothing.
191
192     my ( $fh, $offset, $line, $s_beg, $bucket, $index );
193
194     $fh = Maasha::Filesys::file_read_open( $file );
195
196     $offset = 0;
197
198     while ( $line = <$fh> )
199     {
200         ( undef, $s_beg ) = split "\t", $line, 3;
201
202         $bucket = int( $s_beg / BUCKET_SIZE ); 
203
204         $index->[ $bucket ]->[ COUNT ]++;
205         $index->[ $bucket ]->[ OFFSET ] = $offset if not defined $index->[ $bucket ]->[ OFFSET ];
206
207         $offset += length $line;
208     }
209
210     close $fh;
211
212     Maasha::KISS::kiss_index_store( "$file.index", $index );
213 }
214
215
216 sub kiss_index_offset
217 {
218     # Martin A. Hansen, December 2009.
219
220     # Given a KISS index and a begin position,
221     # locate the offset closest to the begin position,
222     # and return this.
223
224     my ( $index,    # KISS index
225          $beg,      # begin position
226        ) = @_;
227
228     # Returns a number.
229
230     my ( $bucket );
231
232     Maasha::Common::error( qq(Negative begin position: "$beg") ) if $beg < 0;
233
234     $bucket = int( $beg / BUCKET_SIZE ); 
235
236     $bucket = scalar @{ $index } if $bucket > scalar @{ $index };
237
238     while ( $bucket >= 0 )
239     {
240         return $index->[ $bucket ]->[ OFFSET ] if defined $index->[ $bucket ];
241
242         $bucket--;
243     }
244 }
245
246
247 sub kiss_index_count
248 {
249     # Martin A. Hansen, December 2009.
250
251     # Given a KISS index and a begin/end interval
252     # sum the number of counts in that interval,
253     # and return this.
254
255     my ( $index,   # KISS index
256          $beg,     # Begin position
257          $end,     # End position
258        ) = @_;
259
260     # Returns a number.
261
262     my ( $bucket_beg, $bucket_end, $count, $i );
263
264     Maasha::Common::error( qq(Negative begin position: "$beg") ) if $beg < 0;
265
266     $bucket_beg = int( $beg / BUCKET_SIZE );
267     $bucket_end = int( $end / BUCKET_SIZE );
268
269     $bucket_end = scalar @{ $index } if $bucket_end > scalar @{ $index };
270
271     $count = 0;
272
273     for ( $i = $bucket_beg; $i <= $bucket_end; $i++ ) {
274         $count += $index->[ $i ]->[ COUNT ] if defined $index->[ $i ];
275     }
276
277     return $count;
278 }
279
280
281 sub kiss_index_count_nc
282 {
283     # Martin A. Hansen, December 2009.
284
285     # Given a KISS index and a begin/end interval
286     # sum the number of counts in that interval,
287     # and return this.
288
289     my ( $index,   # KISS index
290          $beg,     # Begin position
291          $end,     # End position
292        ) = @_;
293
294     # Returns a number.
295
296     my ( $count );
297
298     Maasha::Common::error( qq(Negative begin position: "$beg") ) if $beg < 0;
299
300     $count = Maasha::NClist::nc_list_count_interval( $index, $beg, $end, INDEX_BEG, INDEX_END, INDEX );
301
302     return $count;
303 }
304
305
306 sub kiss_index_get_entries
307 {
308     # Martin A. Hansen, November 2009.
309
310     # Given a path to a KISS file and a KISS index
311     # along with a beg/end interval, locate all entries
312     # in that interval and return those.
313
314     my ( $file,    # path to KISS file
315          $index,   # KISS index
316          $beg,     # interval begin
317          $end,     # interval end
318        ) = @_;
319
320     # Returns a list.
321
322     my ( $offset, $fh, $entry, @entries );
323
324     # $offset = kiss_index_offset( $index, $beg );
325
326     $fh = Maasha::Filesys::file_read_open( $file );
327
328     # sysseek( $fh, $offset, 0 );
329
330     while ( $entry = Maasha::KISS::kiss_entry_get( $fh ) )
331     {
332         push @entries, $entry if $entry->[ S_END ] > $beg;
333         
334         last if $entry->[ S_BEG ] > $end;
335     }
336
337     close $fh;
338
339     return wantarray ? @entries : \@entries;
340 }
341
342
343 sub kiss_index_get_entries_OLD
344 {
345     # Martin A. Hansen, November 2009.
346
347     # Given a path to a KISS file and a KISS index
348     # along with a beg/end interval, locate all entries
349     # in that interval and return those.
350
351     my ( $file,    # path to KISS file
352          $index,   # KISS index
353          $beg,     # interval begin
354          $end,     # interval end
355        ) = @_;
356
357     # Returns a list.
358
359     my ( $offset, $fh, $entry, @entries );
360
361     $offset = kiss_index_offset( $index, $beg );
362
363     $fh = Maasha::Filesys::file_read_open( $file );
364
365     sysseek( $fh, $offset, 0 );
366
367     while ( $entry = Maasha::KISS::kiss_entry_get( $fh ) )
368     {
369         push @entries, $entry if $entry->[ S_END ] > $beg;
370         
371         last if $entry->[ S_BEG ] > $end;
372     }
373
374     close $fh;
375
376     return wantarray ? @entries : \@entries;
377 }
378
379
380 sub kiss_index_get_blocks
381 {
382     # Martin A. Hansen, December 2009.
383
384     # Given a KISS index returns blocks from this in a 
385     # given position interval. Blocks consisting of
386     # hashes of BEG/END/COUNT.
387
388     my ( $index,   # KISS index node
389          $beg,     # interval begin
390          $end,     # interval end
391        ) = @_;
392
393     # Returns a list.
394
395     my ( $bucket_beg, $bucket_end, $i, @blocks );
396
397     Maasha::Common::error( qq(Negative begin position: "$beg") ) if $beg < 0;
398
399     $bucket_beg = int( $beg / BUCKET_SIZE ); 
400     $bucket_end = int( $end / BUCKET_SIZE ); 
401
402     $bucket_end = scalar @{ $index } if $bucket_end > scalar @{ $index };
403
404     for ( $i = $bucket_beg; $i <= $bucket_end; $i++ )
405     {
406         if ( defined $index->[ $i ] )
407         {
408             push @blocks, {
409                 BEG   => BUCKET_SIZE * $i,
410                 END   => BUCKET_SIZE * ( $i + 1 ),
411                 COUNT => $index->[ $i ]->[ COUNT ],
412             };
413         }
414     }
415
416     return wantarray ? @blocks : \@blocks;
417 }
418
419
420 sub kiss_intersect
421 {
422     # Martin A. Hansen, December 2009.
423
424     # Given filehandles to two different unsorted KISS files
425     # intersect the entries so that all entries from file1 that
426     # overlap entries in file2 are returned - unless the inverse flag
427     # is given in which case entreis from file1 that does not
428     # overlap any entries in file2 are returned.
429
430     my ( $fh1,       # filehandle to file1
431          $fh2,       # filehandle to file2
432          $inverse,   # flag indicating inverse matching - OPTIONAL
433        ) = @_;
434
435     # Returns a list
436
437     my ( $entry, %lookup, $pos, $overlap, @entries );
438
439     while ( $entry = kiss_entry_get( $fh2 ) ) {
440         map { $lookup{ $_ } = 1 } ( $entry->[ S_BEG ] .. $entry->[ S_END ] );
441     }
442
443     while ( $entry = kiss_entry_get( $fh1 ) )
444     {
445         $overlap = 0;
446
447         foreach $pos ( $entry->[ S_BEG ] .. $entry->[ S_END ] )
448         {
449             if ( exists $lookup{ $pos } )
450             {
451                 $overlap = 1;
452
453                 last;
454             }
455         }
456
457         if ( $overlap and not $inverse ) {
458             push @entries, $entry;
459         } elsif ( not $overlap and $inverse ) {
460             push @entries, $entry;
461         }
462     }
463
464     return wantarray ? @entries : \@entries;
465 }
466
467
468 sub kiss_index_store
469 {
470     # Martin A. Hansen, November 2009.
471
472     # Stores a KISS index to a file.
473
474     my ( $path,    # path to KISS index
475          $index,   # KISS index
476        ) = @_;
477
478     # Returns nothing.
479
480     my ( $fh, $json );
481
482     $json = JSON::XS::encode_json( $index );
483
484     $fh = Maasha::Filesys::file_write_open( $path );
485
486     print $fh $json;
487
488     close $fh;
489 }
490
491
492 sub kiss_index_retrieve
493 {
494     # Martin A. Hansen, November 2009.
495
496     # Retrieves a KISS index from a file.
497
498     my ( $path,   # Path to KISS index
499        ) = @_;
500
501     # Returns a data structure.
502
503     my ( $fh, $json, $index );
504
505     local $/ = undef;
506
507     $fh = Maasha::Filesys::file_read_open( $path );
508
509     $json = <$fh>;
510
511     close $fh;
512
513     $index = JSON::XS::decode_json( $json );
514
515     return wantarray ? @{ $index } : $index;
516 }
517
518
519 sub kiss_align_enc
520 {
521     # Martin A. Hansen, November 2009.
522
523     # Encodes alignment descriptors for two
524     # aligned sequences.
525
526     my ( $s_seq,    # Subject sequence reference
527          $q_seq,    # Query sequence reference
528          $offset,   # alternate offset - OPTIONAL
529        ) = @_;
530
531     # Returns a list
532
533     my ( $i, $s, $q, @align );
534
535     # Maasha::Common::error( "Sequence lengths don't match" ) if length ${ $s_seq } != length ${ $q_seq };
536
537     if ( length ${ $s_seq } != length ${ $q_seq } ) {   # for unknown reasons this situation may occur - TODO FIXME
538         return wantarray ? () : [];
539     }
540
541     $offset ||= 0;
542
543     for ( $i = 0; $i < length ${ $s_seq }; $i++ )
544     {
545         $s = uc substr ${ $s_seq }, $i, 1;
546         $q = uc substr ${ $q_seq }, $i, 1;
547
548         if ( $s eq '-' and $q eq '-' )
549         {
550             # do nothing
551         }
552         elsif ( $s eq $q )
553         {
554             $offset++;
555         }
556         else
557         {
558             push @align, "$offset:$s>$q";
559
560             $offset++ if not $s eq '-';
561         }
562     }
563
564     return wantarray ? @align : \@align;
565 }   
566
567
568 sub kiss_align_dec
569 {
570     # Martin A. Hansen, November 2009.
571
572     # Test routine of correct resolve of ALIGN descriptors.
573
574     # S.aur_COL       41      75      5_vnlh2ywXsN1/1 1       -       .       17:A>T,31:A>N   .       .       .       .
575
576     my ( $s_seqref,   # subject sequence reference
577          $entry,      # KISS entry
578        ) = @_;
579
580     # Returns a string.
581
582     my ( $align, $pos, $s_symbol, $q_symbol, $s_seq, $q_seq, $insertions );
583
584     $s_seq = substr ${ $s_seqref }, $entry->{ 'S_BEG' }, $entry->{ 'S_END' } - $entry->{ 'S_BEG' } + 1;
585     $q_seq = $s_seq;
586
587     $insertions = 0;
588
589     foreach $align ( split /,/, $entry->{ 'ALIGN' } )
590     {
591         if ( $align =~ /(\d+):(.)>(.)/ )
592         {
593             $pos      = $1;
594             $s_symbol = $2;
595             $q_symbol = $3;
596
597             if ( $s_symbol eq '-' ) # insertion
598             {
599                 substr $s_seq, $pos + $insertions, 0, $s_symbol;
600                 substr $q_seq, $pos + $insertions, 0, $q_symbol;
601
602                 $insertions++;
603             }
604             elsif ( $q_symbol eq '-' ) # deletion
605             {
606                 substr $q_seq, $pos + $insertions, 1, $q_symbol;
607             }
608             else # mismatch
609             {
610                 substr $q_seq, $pos + $insertions, 1, $q_symbol;
611             }
612         }
613         else
614         {
615             Maasha::Common::error( qq(Bad alignment descriptor: "$align") );
616         }
617     }
618
619     Maasha::Align::align_print_pairwise( [ $entry->{ 'S_ID' }, $s_seq ], [ $entry->{ 'Q_ID' }, $q_seq ] );
620 }
621
622
623 sub kiss2biopiece
624 {
625     # Martin A. Hansen, November 2009.
626
627     # Converts a KISS entry to a Biopiece record.
628
629     my ( $entry,   # KISS entry
630        ) = @_;
631
632     # Returns a hashref
633
634     my ( %record );
635
636     Maasha::Common::error( qq(BAD kiss entry) ) if not scalar @{ $entry } == 12;
637
638     $record{ 'S_ID' }        = $entry->[ S_ID ];
639     $record{ 'S_BEG' }       = $entry->[ S_BEG ];
640     $record{ 'S_END' }       = $entry->[ S_END ];
641     $record{ 'Q_ID' }        = $entry->[ Q_ID ];
642     $record{ 'SCORE' }       = $entry->[ SCORE ];
643     $record{ 'STRAND' }      = $entry->[ STRAND ];
644     $record{ 'HITS' }        = $entry->[ HITS ];
645     $record{ 'ALIGN' }       = $entry->[ ALIGN ];
646     $record{ 'BLOCK_COUNT' } = $entry->[ BLOCK_COUNT ];
647     $record{ 'BLOCK_BEGS' }  = $entry->[ BLOCK_BEGS ];
648     $record{ 'BLOCK_LENS' }  = $entry->[ BLOCK_LENS ];
649     $record{ 'BLOCK_TYPE' }  = $entry->[ BLOCK_TYPE ];
650
651     return wantarray ? %record : \%record;
652 }
653
654
655 sub biopiece2kiss
656 {
657     # Martin A. Hansen, November 2009.
658
659     # Converts a Biopiece record to a KISS entry.
660  
661     my ( $record,   # Biopiece record
662        ) = @_;
663
664     # Returns a hashref
665
666     my ( $entry );
667
668     if ( not defined $record->{ 'S_ID' }  and
669          not defined $record->{ 'S_BEG' } and
670          not defined $record->{ 'S_END' } )
671     {
672         return undef;
673     }
674
675     $entry->[ S_ID ]        = $record->{ 'S_ID' };
676     $entry->[ S_BEG ]       = $record->{ 'S_BEG' };
677     $entry->[ S_END ]       = $record->{ 'S_END' };
678     $entry->[ Q_ID ]        = $record->{ 'Q_ID' }        || ".";
679     $entry->[ SCORE ]       = $record->{ 'SCORE' }       || $record->{ 'BIT_SCORE' } || ".";
680     $entry->[ STRAND ]      = $record->{ 'STRAND' }      || ".";
681     $entry->[ HITS ]        = $record->{ 'HITS' }        || ".";
682     $entry->[ ALIGN ]       = $record->{ 'ALIGN' }       || $record->{ 'DESCRIPTOR' } || ".";
683     $entry->[ BLOCK_COUNT ] = $record->{ 'BLOCK_COUNT' } || ".";
684     $entry->[ BLOCK_BEGS ]  = $record->{ 'BLOCK_BEGS' }  || ".";
685     $entry->[ BLOCK_LENS ]  = $record->{ 'BLOCK_LENS' }  || ".";
686     $entry->[ BLOCK_TYPE ]  = $record->{ 'BLOCK_TYPE' }  || ".";
687
688     return wantarray ? @{ $entry } : $entry;
689 }
690
691
692 # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
693
694 1;
695
696 __END__
697
698 sub kiss_index_nc
699 {
700     # Martin A. Hansen, February 2010.
701
702     # Creates a NC list index of a sorted KISS file.
703
704     my ( $file,         # path to KISS file
705        ) = @_;
706
707     # Returns nothing.
708
709     my ( $fh, $line, @fields, $nc_list );
710
711     $fh = Maasha::Filesys::file_read_open( $file );
712
713     while ( $line = <$fh> )
714     {
715         chomp $line;
716
717         @fields = split "\t", $line;
718
719         if ( not defined $nc_list ) {
720             $nc_list = [ [ @fields ] ];
721         } else {
722             Maasha::NClist::nc_list_add( $nc_list, [ @fields ], INDEX_END, INDEX );
723         }
724     }
725
726     close $fh;
727
728     Maasha::NClist::nc_list_store( $nc_list, "$file.json" );
729 }
730
731
732 sub kiss_index_get_entries_nc
733 {
734     # Martin A. Hansen, November 2009.
735
736     # Given a path to a KISS file and a KISS index
737     # along with a beg/end interval, locate all entries
738     # in that interval and return those.
739
740     my ( $index,   # KISS index
741          $beg,     # interval begin
742          $end,     # interval end
743        ) = @_;
744
745     # Returns a list.
746
747     my ( $features );
748
749     $features = Maasha::NClist::nc_list_get_interval( $index, $beg, $end, INDEX_BEG, INDEX_END, INDEX );
750
751     return wantarray ? @{ $features } : $features;
752 }
753
754
755 sub kiss_index_retrieve_nc
756 {
757     # Martin A. Hansen, November 2009.
758
759     # Retrieves a KISS index from a file.
760
761     my ( $path,   # Path to KISS index
762        ) = @_;
763
764     # Returns a data structure.
765
766     my ( $index );
767
768     $index = Maasha::NClist::nc_list_retrieve( $path );
769
770     return wantarray ? @{ $index } : $index;
771 }