File Coverage

Bio/SearchIO/psl.pm
Criterion Covered Total %
statement 139 167 83.2
branch 32 50 64.0
condition 17 29 58.6
subroutine 16 20 80.0
pod 11 12 91.6
total 215 278 77.3


line stmt bran cond sub pod time code
1             #
2             # BioPerl module for Bio::SearchIO::psl
3             #
4             # Please direct questions and support issues to
5             #
6             # Cared for by Jason Stajich
7             #
8             # Copyright Jason Stajich
9             #
10             # You may distribute this module under the same terms as perl itself
11              
12             # POD documentation - main docs before the code
13              
14             =head1 NAME
15              
16             Bio::SearchIO::psl - A parser for PSL output (UCSC)
17              
18             =head1 SYNOPSIS
19              
20             use Bio::SearchIO;
21             my $parser = Bio::SearchIO->new(-file => 'file.psl',
22             -format => 'psl');
23             while( my $result = $parser->next_result ) {
24             }
25              
26             =head1 DESCRIPTION
27              
28             This is a SearchIO driver for PSL format.
29             PSL format is documented here:
30             http://genome.ucsc.edu/goldenPath/help/customTrack.html#PSL
31              
32             By default it assumes PSL output came from BLAT you can override that
33             by specifying -program_name =E 'BLASTZ' when initializing the
34             SearchIO object.
35              
36             =head1 FEEDBACK
37              
38             =head2 Mailing Lists
39              
40             User feedback is an integral part of the evolution of this and other
41             Bioperl modules. Send your comments and suggestions preferably to
42             the Bioperl mailing list. Your participation is much appreciated.
43              
44             bioperl-l@bioperl.org - General discussion
45             http://bioperl.org/wiki/Mailing_lists - About the mailing lists
46              
47             =head2 Support
48              
49             Please direct usage questions or support issues to the mailing list:
50              
51             I
52              
53             rather than to the module maintainer directly. Many experienced and
54             reponsive experts will be able look at the problem and quickly
55             address it. Please include a thorough description of the problem
56             with code and data examples if at all possible.
57              
58             =head2 Reporting Bugs
59              
60             Report bugs to the Bioperl bug tracking system to help us keep track
61             of the bugs and their resolution. Bug reports can be submitted via the
62             web:
63              
64             https://github.com/bioperl/bioperl-live/issues
65              
66             =head1 AUTHOR - Jason Stajich
67              
68             Email jason-at-bioperl-dot-org
69              
70             =head1 APPENDIX
71              
72             The rest of the documentation details each of the object methods.
73             Internal methods are usually preceded with a _
74              
75             =cut
76              
77             # Let the code begin...
78              
79             package Bio::SearchIO::psl;
80 1     1   7 use vars qw(%MAPPING %MODEMAP $DEFAULT_WRITER_CLASS $DefaultProgramName);
  1         2  
  1         66  
81              
82 1     1   6 use strict;
  1         1  
  1         20  
83 1     1   259 use Bio::Search::HSP::HSPFactory;
  1         2  
  1         23  
84 1     1   256 use Bio::Search::Hit::HitFactory;
  1         2  
  1         23  
85 1     1   255 use Bio::Search::Result::ResultFactory;
  1         2  
  1         98  
86              
87             $DefaultProgramName = 'BLAT';
88             $DEFAULT_WRITER_CLASS = 'Bio::SearchIO::Writer::HitTableWriter';
89              
90             # mapping of terms to Bioperl hash keys
91             %MODEMAP = (
92             'PSLOutput' => 'result',
93             'Result' => 'result',
94             'Hit' => 'hit',
95             'Hsp' => 'hsp'
96             );
97              
98             %MAPPING = (
99             'Hsp_bit-score' => 'HSP-bits',
100             'Hsp_score' => 'HSP-score',
101             'Hsp_evalue' => 'HSP-evalue',
102             'Hsp_query-from' => 'HSP-query_start',
103             'Hsp_query-to' => 'HSP-query_end',
104             'Hsp_hit-from' => 'HSP-hit_start',
105             'Hsp_hit-to' => 'HSP-hit_end',
106             'Hsp_positive' => 'HSP-conserved',
107             'Hsp_identity' => 'HSP-identical',
108             'Hsp_mismatches' => 'HSP-mismatches',
109             'Hsp_qgapblocks' => 'HSP-query_gapblocks',
110             'Hsp_hgapblocks' => 'HSP-hit_gapblocks',
111             'Hsp_gaps' => 'HSP-hsp_gaps',
112             'Hsp_hitgaps' => 'HSP-hit_gaps',
113             'Hsp_querygaps' => 'HSP-query_gaps',
114             'Hsp_align-len' => 'HSP-hsp_length',
115             'Hsp_query-frame' => 'HSP-query_frame',
116             'Hsp_hit-frame' => 'HSP-hit_frame',
117              
118             'Hit_id' => 'HIT-name',
119             'Hit_len' => 'HIT-length',
120             'Hit_accession' => 'HIT-accession',
121             'Hit_def' => 'HIT-description',
122             'Hit_signif' => 'HIT-significance',
123             'Hit_score' => 'HIT-score',
124             'Hit_bits' => 'HIT-bits',
125              
126             'PSLOutput_program' => 'RESULT-algorithm_name',
127             'PSLOutput_version' => 'RESULT-algorithm_version',
128             'PSLOutput_query-def' => 'RESULT-query_name',
129             'PSLOutput_query-len' => 'RESULT-query_length',
130             'PSLOutput_query-acc' => 'RESULT-query_accession',
131             'PSLOutput_querydesc' => 'RESULT-query_description',
132             'PSLOutput_db' => 'RESULT-database_name',
133             'PSLOutput_db-len' => 'RESULT-database_entries',
134             'PSLOutput_db-let' => 'RESULT-database_letters',
135             );
136              
137 1     1   5 use base qw(Bio::SearchIO);
  1         2  
  1         1616  
138              
139             =head2 new
140              
141             Title : new
142             Usage : my $obj = Bio::SearchIO::psl->new();
143             Function: Builds a new Bio::SearchIO::psl object
144             Returns : an instance of Bio::SearchIO::psl
145             Args :
146              
147              
148             =cut
149              
150             sub _initialize {
151 3     3   14 my ( $self, @args ) = @_;
152 3         20 $self->SUPER::_initialize(@args);
153 3         12 my ($pname) = $self->_rearrange( [qw(PROGRAM_NAME)], @args );
154 3   33     30 $self->program_name( $pname || $DefaultProgramName );
155 3         17 $self->_eventHandler->register_factory(
156             'result',
157             Bio::Search::Result::ResultFactory->new(
158             -type => 'Bio::Search::Result::GenericResult'
159             )
160             );
161              
162 3         9 $self->_eventHandler->register_factory(
163             'hit',
164             Bio::Search::Hit::HitFactory->new(
165             -type => 'Bio::Search::Hit::GenericHit'
166             )
167             );
168 3         11 $self->_eventHandler->register_factory(
169             'hsp',
170             Bio::Search::HSP::HSPFactory->new(
171             -type => 'Bio::Search::HSP::PSLHSP'
172             )
173             );
174             }
175              
176             =head2 next_result
177              
178             Title : next_result
179             Usage : my $result = $parser->next_result
180             Function: Parse the next result from the data stream
181             Returns : L or undef if no more results
182             Args : none
183              
184             =cut
185              
186             sub next_result {
187 4     4 1 73 my ($self) = @_;
188 4         10 my ( $lastquery, $lasthit );
189 4         20 local $/ = "\n";
190 4         9 local $_;
191              
192             # skip over any header lines
193 4   100     21 while( defined($_ = $self->_readline) and ! /^\d+\s+\d+\s+/ ) {}
194 4         19 $self->_pushback($_);
195              
196             # now start the main parsing loop
197 4         13 while ( defined( $_ = $self->_readline ) ) {
198             my (
199 6         49 $matches, $mismatches, $rep_matches, $n_count,
200             $q_num_insert, $q_base_insert, $t_num_insert, $t_base_insert,
201             $strand, $q_name, $q_length, $q_start,
202             $q_end, $t_name, $t_length, $t_start,
203             $t_end, $block_count, $block_sizes, $q_starts,
204             $t_starts
205             ) = split;
206              
207 6 50       25 $q_length > 0 or $self->throw("parse error, invalid query length '$q_length'");
208 6         70 my $score = sprintf( "%.2f", 100 * ( $matches + $mismatches + $rep_matches ) / $q_length );
209              
210             # this is overall percent identity...
211 6         11 my $match_total = $matches + $mismatches + $rep_matches;
212 6 50       13 $match_total > 0
213             or $self->throw("parse error, matches + mismatches + rep_matches must be > 0!");
214 6         25 my $percent_id = sprintf("%.2f", 100 * ( $matches + $rep_matches ) / $match_total );
215              
216             # Remember Jim's code is 0 based
217 6 100 100     30 if ( defined $lastquery
    100          
    50          
218             && $lastquery ne $q_name )
219             {
220 1         5 $self->end_element( { 'Name' => 'Hit' } );
221 1         6 $self->end_element( { 'Name' => 'PSLOutput' } );
222 1         4 $self->_pushback($_);
223 1         3 return $self->end_document;
224             }
225             elsif ( !defined $lastquery ) {
226 2         7 $self->{'_result_count'}++;
227 2         12 $self->start_element( { 'Name' => 'PSLOutput' } );
228 2         7 $self->element(
229             {
230             'Name' => 'PSLOutput_program',
231             'Data' => $self->program_name
232             }
233             );
234 2         10 $self->element(
235             {
236             'Name' => 'PSLOutput_query-def',
237             'Data' => $q_name
238             }
239             );
240 2         9 $self->element(
241             {
242             'Name' => 'PSLOutput_query-len',
243             'Data' => $q_length
244             }
245             );
246 2         9 $self->start_element( { 'Name' => 'Hit' } );
247 2         9 $self->element(
248             {
249             'Name' => 'Hit_id',
250             'Data' => $t_name
251             }
252             );
253 2         9 $self->element(
254             {
255             'Name' => 'Hit_len',
256             'Data' => $t_length
257             }
258             );
259 2         8 $self->element(
260             {
261             'Name' => 'Hit_score',
262             'Data' => $score
263             }
264             );
265             }
266             elsif ( $lasthit ne $t_name ) {
267 0         0 $self->end_element( { 'Name' => 'Hit' } );
268 0         0 $self->start_element( { 'Name' => 'Hit' } );
269 0         0 $self->element(
270             {
271             'Name' => 'Hit_id',
272             'Data' => $t_name
273             }
274             );
275 0         0 $self->element(
276             {
277             'Name' => 'Hit_len',
278             'Data' => $t_length
279             }
280             );
281 0         0 $self->element(
282             {
283             'Name' => 'Hit_score',
284             'Data' => $score
285             }
286             );
287             }
288              
289 5         11 my $identical = $matches + $rep_matches;
290 5         15 $self->start_element( { 'Name' => 'Hsp' } );
291 5         22 $self->element(
292             {
293             'Name' => 'Hsp_score',
294             'Data' => $score
295             }
296             );
297 5         19 $self->element(
298             {
299             'Name' => 'Hsp_identity',
300             'Data' => $identical
301             }
302             );
303 5         25 $self->element(
304             {
305             'Name' => 'Hsp_positive',
306             'Data' => $identical
307             }
308             );
309 5         21 $self->element(
310             {
311             'Name' => 'Hsp_mismatches',
312             'Data' => $mismatches
313             }
314             );
315 5         24 $self->element(
316             {
317             'Name' => 'Hsp_gaps',
318             'Data' => $q_base_insert + $t_base_insert
319             }
320             );
321              
322             # query gaps are the number of target inserts and vice-versa
323 5         18 $self->element(
324             {
325             'Name' => 'Hsp_querygaps',
326             'Data' => $t_base_insert
327             }
328             );
329 5         18 $self->element(
330             {
331             'Name' => 'Hsp_hitgaps',
332             'Data' => $q_base_insert
333             }
334             );
335 5 100       34 if ( $strand eq '+' ) {
336 3         18 $self->element(
337             {
338             'Name' => 'Hsp_query-from',
339             'Data' => $q_start + 1
340             }
341             );
342 3         12 $self->element(
343             {
344             'Name' => 'Hsp_query-to',
345             'Data' => $q_end
346             }
347             );
348             }
349             else {
350 2         13 $self->element(
351             {
352             'Name' => 'Hsp_query-to',
353             'Data' => $q_start + 1
354             }
355             );
356 2         8 $self->element(
357             {
358             'Name' => 'Hsp_query-from',
359             'Data' => $q_end
360             }
361             );
362             }
363              
364 5         26 my $hsplen =
365             ($q_base_insert +
366             $t_base_insert +
367             abs( $t_end - $t_start ) +
368             abs( $q_end - $q_start ))/2;
369              
370 5         21 $self->element(
371             {
372             'Name' => 'Hsp_hit-from',
373             'Data' => $t_start + 1
374             }
375             );
376 5         18 $self->element(
377             {
378             'Name' => 'Hsp_hit-to',
379             'Data' => $t_end
380             }
381             );
382 5         18 $self->element(
383             {
384             'Name' => 'Hsp_align-len',
385             'Data' => $hsplen
386             }
387             );
388              
389             # cleanup trailing commas in some output
390 5         25 $block_sizes =~ s/\,$//;
391 5         16 $q_starts =~ s/\,$//;
392 5         16 $t_starts =~ s/\,$//;
393 5         25 my @blocksizes = split( /,/, $block_sizes ); # block sizes
394 5         16 my @qstarts = split( /,/, $q_starts ); # starting position of each block
395             # in query
396 5         18 my @tstarts = split( /,/, $t_starts ); # starting position of each block
397             # in target
398 5         7 my ( @qgapblocks, @hgapblocks );
399              
400 5         18 for ( my $i = 0 ; $i < $block_count ; $i++ ) {
401 38 100       51 if ( $strand eq '+' ) {
402 30         61 push @qgapblocks, [ $qstarts[$i] + 1, $blocksizes[$i] ];
403             }
404             else {
405 8         19 push @qgapblocks, [ $q_length - $qstarts[$i], $blocksizes[$i] ];
406             }
407 38         84 push @hgapblocks, [ $tstarts[$i] + 1, $blocksizes[$i] ];
408             }
409 5         20 $self->element(
410             {
411             'Name' => 'Hsp_qgapblocks',
412             'Data' => \@qgapblocks
413             }
414             );
415 5         19 $self->element(
416             {
417             'Name' => 'Hsp_hgapblocks',
418             'Data' => \@hgapblocks
419             }
420             );
421 5         15 $self->end_element( { 'Name' => 'Hsp' } );
422 5         10 $lastquery = $q_name;
423 5         23 $lasthit = $t_name;
424             }
425 3 100 66     37 if ( defined $lasthit || defined $lastquery ) {
426 1         5 $self->end_element( { 'Name' => 'Hit' } );
427 1         5 $self->end_element( { 'Name' => 'Result' } );
428 1         4 return $self->end_document;
429             }
430             }
431              
432             =head2 start_element
433              
434             Title : start_element
435             Usage : $eventgenerator->start_element
436             Function: Handles a start element event
437             Returns : none
438             Args : hashref with at least 2 keys 'Data' and 'Name'
439              
440              
441             =cut
442              
443             sub start_element {
444 91     91 1 110 my ( $self, $data ) = @_;
445              
446             # we currently don't care about attributes
447 91         105 my $nm = $data->{'Name'};
448 91 100       149 if ( my $type = $MODEMAP{$nm} ) {
449 9         23 $self->_mode($type);
450 9 50       21 if ( $self->_eventHandler->will_handle($type) ) {
451 9         20 my $func = 'start_'.lc $type;
452 9         13 $self->_eventHandler->$func( $data->{'Attributes'} );
453             }
454 9         15 unshift @{ $self->{'_elements'} }, $type;
  9         22  
455             }
456 91 100       146 if ( $nm eq 'PSLOutput' ) {
457 2         5 $self->{'_values'} = {};
458 2         5 $self->{'_result'} = undef;
459 2         6 $self->{'_mode'} = '';
460             }
461              
462             }
463              
464             =head2 end_element
465              
466             Title : end_element
467             Usage : $eventgenerator->end_element
468             Function: Handles an end element event
469             Returns : return value from the associated end_$type event handler
470             Args : hashref with at least 2 keys 'Data' and 'Name'
471              
472              
473             =cut
474              
475             sub end_element {
476 91     91 1 108 my ( $self, $data ) = @_;
477 91         101 my $nm = $data->{'Name'};
478 91         89 my $rc;
479              
480             # Hsp are sort of weird, in that they end when another
481             # object begins so have to detect this in end_element for now
482              
483 91 100       183 if ( my $type = $MODEMAP{$nm} ) {
    50          
484 9 50       21 if ( $self->_eventHandler->will_handle($type) ) {
485 9         21 my $func = 'end_'.lc $type;
486             $rc = $self->_eventHandler->$func( $self->{'_reporttype'},
487 9         15 $self->{'_values'} );
488             }
489 9         12 shift @{ $self->{'_elements'} };
  9         14  
490              
491             }
492             elsif ( $MAPPING{$nm} ) {
493 82 50       114 if ( ref( $MAPPING{$nm} ) =~ /hash/i ) {
494 0         0 my $key = ( keys %{ $MAPPING{$nm} } )[0];
  0         0  
495             $self->{'_values'}->{$key}->{ $MAPPING{$nm}->{$key} } =
496 0         0 $self->{'_last_data'};
497             }
498             else {
499 82         160 $self->{'_values'}->{ $MAPPING{$nm} } = $self->{'_last_data'};
500             }
501             }
502             else {
503 0         0 $self->warn(
504             __PACKAGE__ . "::end_element: unknown nm '$nm', ignoring\n" );
505             }
506 91         106 $self->{'_last_data'} = ''; # remove read data if we are at
507             # end of an element
508             $self->{'_result'} = $rc
509             if ( defined $nm
510             && defined $MODEMAP{$nm}
511 91 100 66     249 && $MODEMAP{$nm} eq 'result' );
      100        
512 91         119 return $rc;
513              
514             }
515              
516             =head2 element
517              
518             Title : element
519             Usage : $eventhandler->element({'Name' => $name, 'Data' => $str});
520             Function: Convience method that calls start_element, characters, end_element
521             Returns : none
522             Args : Hash ref with the keys 'Name' and 'Data'
523              
524              
525             =cut
526              
527             sub element {
528 82     82 1 102 my ( $self, $data ) = @_;
529 82         120 $self->start_element($data);
530 82         131 $self->characters($data);
531 82         120 $self->end_element($data);
532             }
533              
534             =head2 characters
535              
536             Title : characters
537             Usage : $eventgenerator->characters($str)
538             Function: Send a character events
539             Returns : none
540             Args : string
541              
542              
543             =cut
544              
545             sub characters {
546 82     82 1 88 my ( $self, $data ) = @_;
547              
548 82 50       122 return unless ( defined $data->{'Data'} );
549 82 50       194 if ( $data->{'Data'} =~ /^\s+$/ ) {
550 0 0       0 return unless $data->{'Name'} =~ /Hsp\_(midline|qseq|hseq)/;
551             }
552              
553 82 50 66     109 if ( $self->in_element('hsp')
554             && $data->{'Name'} =~ /Hsp\_(qseq|hseq|midline)/ )
555             {
556              
557 0         0 $self->{'_last_hspdata'}->{ $data->{'Name'} } .= $data->{'Data'};
558             }
559              
560 82         157 $self->{'_last_data'} = $data->{'Data'};
561             }
562              
563             =head2 _mode
564              
565             Title : _mode
566             Usage : $obj->_mode($newval)
567             Function:
568             Example :
569             Returns : value of _mode
570             Args : newvalue (optional)
571              
572              
573             =cut
574              
575             sub _mode {
576 9     9   12 my ( $self, $value ) = @_;
577 9 50       15 if ( defined $value ) {
578 9         14 $self->{'_mode'} = $value;
579             }
580 9         12 return $self->{'_mode'};
581             }
582              
583             =head2 within_element
584              
585             Title : within_element
586             Usage : if( $eventgenerator->within_element($element) ) {}
587             Function: Test if we are within a particular element
588             This is different than 'in' because within can be tested
589             for a whole block.
590             Returns : boolean
591             Args : string element name
592              
593              
594             =cut
595              
596             sub within_element {
597 0     0 1 0 my ( $self, $name ) = @_;
598             return 0
599             if (!defined $name && !defined $self->{'_elements'}
600 0 0 0     0 || scalar @{ $self->{'_elements'} } == 0 );
  0   0     0  
601 0         0 foreach ( @{ $self->{'_elements'} } ) {
  0         0  
602 0 0       0 if ( $_ eq $name ) {
603 0         0 return 1;
604             }
605             }
606 0         0 return 0;
607             }
608              
609             =head2 in_element
610              
611             Title : in_element
612             Usage : if( $eventgenerator->in_element($element) ) {}
613             Function: Test if we are in a particular element
614             This is different than 'in' because within can be tested
615             for a whole block.
616             Returns : boolean
617             Args : string element name
618              
619              
620             =cut
621              
622             sub in_element {
623 82     82 1 104 my ( $self, $name ) = @_;
624 82 50       133 return 0 if !defined $self->{'_elements'}->[0];
625 82         329 return ( $self->{'_elements'}->[0] eq $name );
626             }
627              
628             =head2 start_document
629              
630             Title : start_document
631             Usage : $eventgenerator->start_document
632             Function: Handles a start document event
633             Returns : none
634             Args : none
635              
636              
637             =cut
638              
639             sub start_document {
640 0     0 1 0 my ($self) = @_;
641 0         0 $self->{'_lasttype'} = '';
642 0         0 $self->{'_values'} = {};
643 0         0 $self->{'_result'} = undef;
644 0         0 $self->{'_mode'} = '';
645 0         0 $self->{'_elements'} = [];
646             }
647              
648             =head2 end_document
649              
650             Title : end_document
651             Usage : $eventgenerator->end_document
652             Function: Handles an end document event
653             Returns : Bio::Search::Result::ResultI object
654             Args : none
655              
656              
657             =cut
658              
659             sub end_document {
660 2     2 1 6 my ( $self, @args ) = @_;
661 2         13 return $self->{'_result'};
662             }
663              
664             =head2 result_count
665              
666             Title : result_count
667             Usage : my $count = $searchio->result_count
668             Function: Returns the number of results we have processed
669             Returns : integer
670             Args : none
671              
672              
673             =cut
674              
675             sub result_count {
676 0     0 1 0 my $self = shift;
677 0         0 return $self->{'_result_count'};
678             }
679              
680 0     0 0 0 sub report_count { shift->result_count }
681              
682             =head2 program_name
683              
684             Title : program_name
685             Usage : $obj->program_name($newval)
686             Function: Get/Set the program name
687             Returns : value of program_name (a scalar)
688             Args : on set, new value (a scalar or undef, optional)
689              
690              
691             =cut
692              
693             sub program_name {
694 5     5 1 11 my $self = shift;
695              
696 5 100       16 $self->{'program_name'} = shift if @_;
697 5   50     28 return $self->{'program_name'} || $DefaultProgramName;
698             }
699              
700             1;