t/AlignIO/AlignIO.t: fix number of tests in plan (fixup c523e6bed866)
[bioperl-live.git] / Bio / Search / Result / BlastResult.pm
blobc70d1be3c916aff1b3d988344fdc3c96138b85ed
2 # BioPerl module for Bio::Search::Result::BlastResult
4 # Please direct questions and support issues to <bioperl-l@bioperl.org>
6 # Cared for by Steve Chervitz <sac@bioperl.org>
8 # Copyright Steve Chervitz
10 # You may distribute this module under the same terms as perl itself
12 # POD documentation - main docs before the code
14 =head1 NAME
16 Bio::Search::Result::BlastResult - Blast-specific subclass of Bio::Search::Result::GenericResult
18 =head1 SYNOPSIS
20 # Working with iterations (PSI-BLAST results)
22 $result->next_iteration();
23 $result->num_iterations();
24 $result->iteration();
25 $result->iterations();
27 # See Bio::Search::Result::GenericResult for information about working with Results.
29 # See L<Bio::Search::Iteration::IterationI|Bio::Search::Iteration::IterationI>
30 # for details about working with iterations.
32 # TODO:
33 # * Show how to configure a SearchIO stream so that it generates
34 # BlastResult objects.
37 =head1 DESCRIPTION
39 This object is a subclass of Bio::Search::Result::GenericResult
40 and provides some operations that facilitate working with BLAST
41 and PSI-BLAST results.
43 For general information about working with Results, see
44 Bio::Search::Result::GenericResult.
46 =head1 FEEDBACK
48 =head2 Mailing Lists
50 User feedback is an integral part of the evolution of this and other
51 Bioperl modules. Send your comments and suggestions preferably to
52 the Bioperl mailing list. Your participation is much appreciated.
54 bioperl-l@bioperl.org - General discussion
55 http://bioperl.org/wiki/Mailing_lists - About the mailing lists
57 =head2 Support
59 Please direct usage questions or support issues to the mailing list:
61 I<bioperl-l@bioperl.org>
63 rather than to the module maintainer directly. Many experienced and
64 reponsive experts will be able look at the problem and quickly
65 address it. Please include a thorough description of the problem
66 with code and data examples if at all possible.
68 =head2 Reporting Bugs
70 Report bugs to the Bioperl bug tracking system to help us keep track
71 of the bugs and their resolution. Bug reports can be submitted via the
72 web:
74 https://github.com/bioperl/bioperl-live/issues
76 =head1 AUTHOR - Steve Chervitz
78 Email sac@bioperl.org
80 =head1 APPENDIX
82 The rest of the documentation details each of the object methods.
83 Internal methods are usually preceded with a _
85 =cut
88 # Let the code begin...
91 package Bio::Search::Result::BlastResult;
92 use strict;
94 use Bio::Search::BlastStatistics;
96 use base qw(Bio::Search::Result::GenericResult);
98 =head2 new
100 Title : new
101 Usage : my $obj = Bio::Search::Result::BlastResult->new();
102 Function: Builds a new Bio::Search::Result::BlastResult object
103 Returns : Bio::Search::Result::BlastResult
104 Args : See Bio::Search::Result::GenericResult();
105 The following parameters are specific to BlastResult:
106 -iterations => array ref of Bio::Search::Iteration::IterationI objects
107 -inclusion_threshold => e-value threshold for inclusion in the
108 PSI-BLAST score matrix model (blastpgp)
110 =cut
112 sub new {
113 my($class,@args) = @_;
115 my $self = $class->SUPER::new(@args);
117 $self->{'_iterations'} = [];
118 $self->{'_iteration_index'} = 0;
119 $self->{'_iteration_count'} = 0;
121 my( $iters, $ithresh ) = $self->_rearrange([qw(ITERATIONS
122 INCLUSION_THRESHOLD)],@args);
124 $self->{'_inclusion_threshold'} = $ithresh; # This is a read-only variable
126 if( defined $iters ) {
127 $self->throw("Must define arrayref of Iterations when initializing a $class\n") unless ref($iters) =~ /array/i;
129 foreach my $i ( @{$iters} ) {
130 $self->add_iteration($i);
133 else {
134 # This shouldn't get called with the new SearchIO::blast.
135 print STDERR "BlastResult::new(): Not adding iterations.\n";
136 $self->{'_no_iterations'} = 1;
139 return $self;
143 =head2 hits
145 Title : hits
146 Usage : my @hits = $result->hits
147 Function: Returns the available hits for this Result
148 Returns : Array of L<Bio::Search::Hit::HitI> objects
149 Args : none
150 Note : This method overrides L<Bio::Search::Result::GenericResult::hits> to
151 take into account the possibility of multiple iterations, as occurs
152 in PSI-BLAST reports.
153 If there are multiple iterations, all 'new' hits for all iterations
154 are returned. These are the hits that did not occur in a previous
155 iteration.
157 See Also: L<Bio::Search::Result::GenericResult::hits>
159 =cut
161 sub hits {
162 my ($self) = shift;
163 if ($self->{'_no_iterations'}) {
164 return $self->SUPER::hits;
166 my @hits = ();
167 foreach my $it ($self->iterations) {
168 push @hits, $it->hits;
170 return @hits;
173 =head2 next_hit
175 Title : next_hit
176 Usage : while( $hit = $result->next_hit()) { ... }
177 Function: Returns the next available Hit object, representing potential
178 matches between the query and various entities from the database.
179 Returns : a Bio::Search::Hit::HitI object or undef if there are no more.
180 Args : none
181 Note : This method overrides L<Bio::Search::Result::GenericResult::next_hit>
182 to take into account the possibility of multiple iterations, as
183 occurs in PSI-BLAST reports.
185 If there are multiple iterations, calling next_hit() traverses the
186 all of the hits, old and new, for each iteration, calling next_hit()
187 on each iteration.
189 See Also: L<Bio::Search::Iteration::GenericIteration::next_hit>
191 =cut
193 sub next_hit {
194 my ($self,@args) = @_;
195 if ($self->{'_no_iterations'}) {
196 return $self->SUPER::next_hit(@args);
199 my $iter_index;
200 if (not defined $self->{'_last_hit'}) {
201 $iter_index = $self->{'_iter_index'} = $self->_next_iteration_index;
202 } else {
203 $iter_index = $self->{'_iter_index'};
206 return if $iter_index >= scalar @{$self->{'_iterations'}};
208 my $it = $self->{'_iterations'}->[$iter_index];
209 my $hit = $self->{'_last_hit'} = $it->next_hit;
211 return defined($hit) ? $hit : $self->next_hit;
215 =head2 num_hits
217 Title : num_hits
218 Usage : my $hitcount= $result->num_hits
219 Function: returns the number of hits for this query result
220 Returns : integer
221 Args : none
222 Note : This method overrides L<Bio::Search::Result::GenericResult::num_hits>
223 to take into account the possibility of multiple iterations, as
224 occurs in PSI-BLAST reports.
226 If there are multiple iterations, calling num_hits() returns the
227 number of 'new' hits for each iteration. These are the hits that did
228 not occur in a previous iteration.
230 See Also: L<Bio::Search::Result::GenericResult::num_hits>
232 =cut
234 sub num_hits{
235 my ($self) = shift;
236 if ($self->{'_no_iterations'}) {
237 return $self->SUPER::num_hits;
239 if (not defined $self->{'_iterations'}) {
240 $self->throw("Can't get Hits: data not collected.");
242 return scalar( $self->hits );
245 =head2 add_hit
247 Title : add_hit
248 Usage : $report->add_hit($hit)
249 Function: Adds a HitI to the stored list of hits
250 Returns : Number of HitI currently stored
251 Args : Bio::Search::Hit::HitI
253 =cut
255 sub add_hit {
256 my ($self,$hit) = @_;
257 my $iter = $self->iteration;
258 if( $hit->isa('Bio::Search::Hit::HitI') ) {
259 return $iter->add_hit(-hit => $hit);
260 } else {
261 $self->throw("Passed in a " .ref($hit).
262 " as a Iteration which is not a Bio::Search::Hit::HitI.");
264 return $iter->num_hits;
267 =head2 add_iteration
269 Title : add_iteration
270 Usage : $report->add_iteration($iteration)
271 Function: Adds a IterationI to the stored list of iterations
272 Returns : Number of IterationI currently stored
273 Args : Bio::Search::Iteration::IterationI
275 =cut
277 sub add_iteration {
278 my ($self,$i) = @_;
279 if( $i->isa('Bio::Search::Iteration::IterationI') ) {
280 push @{$self->{'_iterations'}}, $i;
281 $self->{'_iteration_count'}++;
282 } else {
283 $self->throw("Passed in a " .ref($i).
284 " as a Iteration which is not a Bio::Search::Iteration::IterationI.");
286 return scalar @{$self->{'_iterations'}};
290 =head2 next_iteration
292 Title : next_iteration
293 Usage : while( $it = $result->next_iteration()) { ... }
294 Function: Returns the next Iteration object, representing all hits
295 found within a given PSI-Blast iteration.
296 Returns : a Bio::Search::Iteration::IterationI object or undef if there are no more.
297 Args : none
299 =cut
301 sub next_iteration {
302 my ($self) = @_;
304 unless($self->{'_iter_queue_started'}) {
305 $self->{'_iter_queue'} = [$self->iterations()];
306 $self->{'_iter_queue_started'} = 1;
308 return shift @{$self->{'_iter_queue'}};
311 =head2 iteration
313 Usage : $iteration = $blast->iteration( $number );
314 Purpose : Get an IterationI object for the specified iteration
315 in the search result (PSI-BLAST).
316 Returns : Bio::Search::Iteration::IterationI object
317 Throws : Bio::Root::NoSuchThing exception if $number is not within
318 range of the number of iterations in this report.
319 Argument : integer (optional, if not specified get the last iteration)
320 First iteration = 1
322 =cut
324 sub iteration {
325 my ($self,$num) = @_;
326 $num = scalar @{$self->{'_iterations'}} unless defined $num;
327 unless ($num >= 1 and $num <= scalar $self->{'_iteration_count'}) {
328 $self->throw(-class=>'Bio::Root::NoSuchThing',
329 -text=>"No such iteration number: $num. Valid range=1-$self->{'_iteration_count'}",
330 -value=>$num);
332 return $self->{'_iterations'}->[$num-1];
335 =head2 num_iterations
337 Usage : $num_iterations = $blast->num_iterations;
338 Purpose : Get the number of iterations in the search result (PSI-BLAST).
339 Returns : Total number of iterations in the report
340 Argument : none (read-only)
342 =cut
344 sub num_iterations { shift->{'_iteration_count'} }
346 # Methods provided for consistency with BPpsilite.pm (now deprecated);
347 # these are now merely synonyms
349 =head2 number_of_iterations
351 Usage : $num_iterations = $blast->number_of_iterations;
352 Purpose : Get the number of iterations in the search result (PSI-BLAST).
353 Returns : Total number of iterations in the report
354 Argument : none (read-only)
355 Note : Alias of L<num_iterations>.
357 =cut
359 sub number_of_iterations { shift->num_iterations }
361 =head2 round
363 Usage : $round = $blast->round( $number );
364 Purpose : Get an IterationI object for the specified iteration
365 in the search result (PSI-BLAST).
366 Returns : Bio::Search::Iteration::IterationI object
367 Throws : Bio::Root::NoSuchThing exception if $number is not within
368 range of the number of iterations in this report.
369 Argument : integer (optional, if not specified get the last iteration)
370 First iteration = 1
371 Note : Alias of L<iteration>.
373 =cut
375 sub round { shift->iteration(@_) }
378 =head2 iterations
380 Title : iterations
381 Usage : my @iterations = $result->iterations
382 Function: Returns the IterationI objects contained within this Result
383 Returns : Array of L<Bio::Search::Iteration::IterationI> objects
384 Args : none
386 =cut
388 sub iterations {
389 my $self = shift;
390 my @its = ();
391 if( ref($self->{'_iterations'}) =~ /ARRAY/i ) {
392 @its = @{$self->{'_iterations'}};
394 return @its;
397 =head2 psiblast
399 Usage : if( $blast->psiblast ) { ... }
400 Purpose : Set/get a boolean indicator whether or not the report
401 is a PSI-BLAST report.
402 Returns : 1 if PSI-BLAST, undef if not.
403 Argument : 1 (when setting)
405 =cut
407 #----------------
408 sub psiblast {
409 #----------------
410 my ($self, $val ) = @_;
411 if( $val ) {
412 $self->{'_psiblast'} = 1;
414 return $self->{'_psiblast'};
418 =head2 no_hits_found
420 Usage : $nohits = $blast->no_hits_found( $iteration_number );
421 Purpose : Get boolean indicator indicating whether or not any hits
422 were present in the report.
424 This is NOT the same as determining the number of hits via
425 the hits() method, which will return zero hits if there were no
426 hits in the report or if all hits were filtered out during the parse.
428 Thus, this method can be used to distinguish these possibilities
429 for hitless reports generated when filtering.
431 Returns : Boolean
432 Argument : (optional) integer indicating the iteration number (PSI-BLAST)
433 If iteration number is not specified and this is a PSI-BLAST result,
434 then this method will return true only if all iterations had
435 no hits found.
437 =cut
439 sub no_hits_found {
440 my ($self, $round) = @_;
442 my $result = 0; # final return value of this method.
443 # Watch the double negative!
444 # result = 0 means "yes hits were found"
445 # result = 1 means "no hits were found" (for the indicated iteration or all iterations)
447 # If a iteration was not specified and there were multiple iterations,
448 # this method should return true only if all iterations had no hits found.
449 if( not defined $round ) {
450 if( $self->{'_iterations'} > 1) {
451 $result = 1;
452 foreach my $i( 1..$self->{'_iterations'} ) {
453 if( not defined $self->{"_iteration_$i"}->{'_no_hits_found'} ) {
454 $result = 0;
455 last;
459 else {
460 $result = $self->{"_iteration_1"}->{'_no_hits_found'};
463 else {
464 $result = $self->{"_iteration_$round"}->{'_no_hits_found'};
467 return $result;
471 =head2 set_no_hits_found
473 Usage : $blast->set_no_hits_found( $iteration_number );
474 Purpose : Set boolean indicator indicating whether or not any hits
475 were present in the report.
476 Returns : n/a
477 Argument : (optional) integer indicating the iteration number (PSI-BLAST)
479 =cut
481 sub set_no_hits_found {
482 my ($self, $round) = @_;
483 $round ||= 1;
484 $self->{"_iteration_$round"}->{'_no_hits_found'} = 1;
487 =head2 _next_iteration_index
489 Title : _next_iteration_index
490 Usage : private
492 =cut
494 sub _next_iteration_index{
495 my ($self,@args) = @_;
496 return $self->{'_iteration_index'}++;
500 =head2 rewind
502 Title : rewind
503 Usage : $result->rewind;
504 Function: Allow one to reset the Iteration iterator to the beginning
505 Since this is an in-memory implementation
506 Returns : none
507 Args : none
509 =cut
511 sub rewind {
512 my $self = shift;
513 $self->SUPER::rewind(@_);
514 $self->{'_iteration_index'} = 0;
515 foreach ($self->iterations) {
516 $_->rewind;
521 =head2 inclusion_threshold
523 Title : inclusion_threshold
524 Usage : my $incl_thresh = $result->inclusion_threshold; (read-only)
525 Function: Gets the e-value threshold for inclusion in the PSI-BLAST
526 score matrix model (blastpgp) that was used for generating the report
527 being parsed.
528 Returns : number (real) or undef if not a PSI-BLAST report.
529 Args : none
531 =cut
533 sub inclusion_threshold {
534 my $self = shift;
535 return $self->{'_inclusion_threshold'};