File Coverage

blib/lib/RNA/HairpinFigure.pm
Criterion Covered Total %
statement 122 243 50.2
branch 24 58 41.3
condition 9 21 42.8
subroutine 6 7 85.7
pod 3 3 100.0
total 164 332 49.4


line stmt bran cond sub pod time code
1             # RNA::HairpinFigure
2             # Maintained by Wei Shen
3              
4             package RNA::HairpinFigure;
5              
6 1     1   17049 use 5.10.0;
  1         3  
  1         36  
7 1     1   4 use strict;
  1         2  
  1         33  
8 1     1   4 use warnings FATAL => 'all';
  1         10  
  1         51  
9 1     1   848 no if $] >= 5.018, warnings => "experimental";
  1         7  
  1         4  
10              
11             # use Carp;
12              
13             require Exporter;
14              
15             our @ISA = qw(Exporter);
16             our @EXPORT = qw(draw make_pair_table make_pair_table_deleting_multi_loops);
17              
18             =head1 NAME
19              
20             RNA::HairpinFigure - Draw hairpin-like text figure from RNA
21             sequence and its secondary structure in dot-bracket notation.
22              
23             =head1 DESCRIPTION
24              
25             miRNA database miRBase maintains miRNAs and their precursors --
26             pre-miRNAs which have hairpin-like secondary structures. They
27             provide the hairpin-like text figure along with sequences and
28             secondary structures in dot-bracket notation which could produced
29             by ViennaRNA package.
30              
31             However, neither miRBase nor ViennaRNA provide any scripts or
32             programs to transfrom dot-bracket notation to hairpin-like text
33             figure, which was needed in our miRNA prediction project.
34              
35             RNA::HairpinFigure draws hairpin-like text figure from RNA
36             sequence and its secondary structure in dot-bracket notation.
37             If the hairpin have multi loops, they will be deleted and
38             treated as a big loop, the longest stem will be the final stem.
39              
40             This module is part of the FOMmiR miRNA predictor on
41             http://bioinf.shenwei.me or http://bioinf.xnyy.cn/.
42              
43             May this module be helpful for you.
44              
45             =head1 VERSION
46              
47             Version 0.140101 released at 1th Jan. 2014.
48              
49             =cut
50              
51             our $VERSION = '0.140101';
52              
53             =head1 SYNOPSIS
54              
55             Usage:
56              
57             use RNA::HairpinFigure qw/draw/;
58              
59             my $name = 'hsa-mir-92a-1 MI0000093 Homo sapiens miR-92a-1 stem-loop';
60             my $seq = 'CUUUCUACACAGGUUGGGAUCGGUUGCAAUGCUGUGUUUCUGUAUGGUAUUGCACUUGUCCCGGCCUGUUGAGUUUGG';
61             my $struct = '..(((...((((((((((((.(((.(((((((((((......)))))))))))))).)))))))))))).))).....';
62            
63             my $figure = draw( $seq, $struct );
64              
65             print ">$name\n$seq\n$struct\n$figure\n";
66              
67             Output:
68              
69             >hsa-mir-92a-1 MI0000093 Homo sapiens miR-92a-1 stem-loop
70             CUUUCUACACAGGUUGGGAUCGGUUGCAAUGCUGUGUUUCUGUAUGGUAUUGCACUUGUCCCGGCCUGUUGAGUUUGG
71             ..(((...((((((((((((.(((.(((((((((((......)))))))))))))).)))))))))))).))).....
72             ---CU UAC C U UU
73             UUC ACAGGUUGGGAU GGU GCAAUGCUGUG U
74             ||| |||||||||||| ||| |||||||||||
75             GAG UGUCCGGCCCUG UCA CGUUAUGGUAU G
76             GGUUU --U U - GU
77              
78              
79             =head1 EXPORT
80              
81             draw make_pair_table make_pair_table_deleting_multi_loops
82              
83             =head1 SUBROUTINES/METHODS
84              
85             =head2 draw SCALAR SCALAR
86              
87             Returns the hairpin-like text figures. Sequence and
88             its secondary structure in dot-bracket notation are
89             both required as arguments.
90              
91             When a hairpin has multi-loops, only the longest stem
92             remains according to miRBase's practice.
93              
94             =cut
95              
96             sub draw($$) {
97 4     4 1 327 my ( $seq, $struct ) = @_;
98              
99 4 100 66     27 return 'Missing sequence or structure'
100             unless length $seq > 0 and length $struct > 0;
101 3 100       11 return 'Missmatch length of sequence and structure'
102             unless length $seq == length $struct;
103 2 100       13 return 'Illegal character in dot-bracket notation'
104             if $struct =~ /[^\.\(\)]/;
105              
106 1         1 my ( @l1, @l2, @l3, @l4, @l5 );
107              
108 1         1 my $len = length $struct;
109 1         2 my $table;
110              
111 1 50       6 if ( $struct =~ /(\)\.*\()/ ) {
112 0         0 $table = make_pair_table_deleting_multi_loops($struct);
113             }
114             else {
115 1         4 $table = make_pair_table($struct);
116             }
117              
118 1         9 my @left = sort { $a <=> $b } keys %$table;
  108         98  
119 1         4 my @right = map { $$table{$_} } @left;
  29         35  
120              
121             # ssRNA
122 1 50       5 if ( $len - $right[0] >= $left[0] - 1 ) {
123 1         4 for ( 1 .. ( $len - $right[0] - ( $left[0] - 1 ) ) ) {
124 3         4 push @l1, '-';
125 3         4 push @l2, ' ';
126 3         3 push @l3, ' ';
127 3         5 push @l4, ' ';
128 3         7 push @l5, substr( $seq, $len - $_, 1 );
129             }
130 1         3 for ( 1 .. ( $left[0] - 1 ) ) {
131 2         4 push @l1, substr( $seq, $_ - 1, 1 );
132 2         3 push @l2, ' ';
133 2         3 push @l3, ' ';
134 2         2 push @l4, ' ';
135 2         7 push @l5,
136             substr( $seq,
137             $len - ( $len - $right[0] - ( $left[0] - 1 ) ) - $_, 1 );
138             }
139             }
140             else {
141 0         0 for ( 1 .. ( $left[0] - 1 - ( $len - $right[0] ) ) ) {
142 0         0 push @l1, substr( $seq, $_ - 1, 1 );
143 0         0 push @l2, ' ';
144 0         0 push @l3, ' ';
145 0         0 push @l4, ' ';
146 0         0 push @l5, '-';
147             }
148 0         0 for ( 1 .. ( $len - $right[0] ) ) {
149 0         0 push @l1, substr( $seq, $_, 1 );
150 0         0 push @l2, ' ';
151 0         0 push @l3, ' ';
152 0         0 push @l4, ' ';
153 0         0 push @l5, substr( $seq, $len - $_, 1 );
154             }
155             }
156              
157             # stem region
158 1         2 my $next5 = $left[0];
159 1         1 my $next3 = $right[0];
160 1         2 my ( $n5, $n3, $asy );
161 1         4 while ( $next5 <= $left[-1] ) {
162              
163             # stem
164 7 100 66     97 if ( $next5 ~~ @left and $next3 ~~ @right ) {
    100 66        
    50 33        
165 4   66     29 while ( $next5 ~~ @left and $next3 ~~ @right ) {
166 29         39 push @l1, ' ';
167 29         48 push @l2, substr( $seq, $next5 - 1, 1 );
168 29         30 push @l3, '|';
169 29         57 push @l4, substr( $seq, $$table{$next5} - 1, 1 );
170 29         56 push @l5, ' ';
171 29         25 $next5++;
172 29         171 $next3--;
173             }
174             }
175              
176             # 5' gap
177             elsif ( $next5 !~ @left and $next3 ~~ @right ) {
178              
179             # print "[5' gap],$next5,$next3\n";
180 1         2 $n5 = 0;
181 1         8 $n5++ until ( $next5 + $n5 ) ~~ @left;
182 1         4 for ( 1 .. $n5 ) {
183 1         13 push @l1, substr( $seq, $next5 + $_ - 2, 1 );
184 1         3 push @l2, ' ';
185 1         2 push @l3, ' ';
186 1         2 push @l4, ' ';
187 1         3 push @l5, '-';
188             }
189 1         3 $next5 += $n5;
190             }
191              
192             # 3' gap
193             elsif ( $next5 ~~ @left and $next3 !~ @right ) {
194              
195             # print "[3' gap], $next5,$next3\n";
196 0         0 $n3 = 0;
197 0         0 $n3++ until ( $next3 - $n3 ) ~~ @right;
198 0         0 for ( 1 .. $n3 ) {
199 0         0 push @l1, '-';
200 0         0 push @l2, ' ';
201 0         0 push @l3, ' ';
202 0         0 push @l4, ' ';
203 0         0 push @l5, substr( $seq, $next3 - $_, 1 );
204             }
205 0         0 $next3 -= $n3;
206             }
207              
208             # bulge
209             else {
210 2         3 $n5 = 0;
211 2         32 $n5++ until ( $next5 + $n5 ) ~~ @left;
212 2         4 $n3 = 0;
213 2         12 $n3++ until ( $next3 - $n3 ) ~~ @right;
214              
215 2 100       6 if ( $n5 > $n3 ) {
    50          
216 1         3 for ( 1 .. ( $n5 - $n3 ) ) {
217 2         5 push @l1, substr( $seq, $next5 + $_ - 2, 1 );
218 2         3 push @l2, ' ';
219 2         3 push @l3, ' ';
220 2         3 push @l4, ' ';
221 2         4 push @l5, '-';
222             }
223 1         2 for ( 1 .. $n3 ) {
224 1         4 push @l1, substr( $seq, $next5 + $n5 - $n3 + $_ - 2,, 1 );
225 1         2 push @l2, ' ';
226 1         2 push @l3, ' ';
227 1         2 push @l4, ' ';
228 1         3 push @l5, substr( $seq, $next3 - $_, 1 );
229             }
230             }
231             elsif ( $n5 < $n3 ) {
232 0         0 for ( 1 .. ( $n3 - $n5 ) ) {
233 0         0 push @l1, '-';
234 0         0 push @l2, ' ';
235 0         0 push @l3, ' ';
236 0         0 push @l4, ' ';
237 0         0 push @l5, substr( $seq, $next3 - $_, 1 );
238             }
239 0         0 for ( 1 .. $n5 ) {
240 0         0 push @l1, substr( $seq, $next5 + $_ - 2, 1 );
241 0         0 push @l2, ' ';
242 0         0 push @l3, ' ';
243 0         0 push @l4, ' ';
244 0         0 push @l5, substr( $seq, $next3 - ( $n3 - $n5 ) - $_, 1 );
245             }
246             }
247             else {
248 1         3 for ( 1 .. $n5 ) {
249 1         4 push @l1, substr( $seq, $next5 + $_ - 2, 1 );
250 1         2 push @l2, ' ';
251 1         2 push @l3, ' ';
252 1         2 push @l4, ' ';
253 1         3 push @l5, substr( $seq, $next3 - $_, 1 );
254             }
255             }
256 2         2 $next5 += $n5;
257 2         6 $next3 -= $n3;
258             }
259             }
260              
261             # terminal loop
262 1         3 my $loop = $right[-1] - $left[-1] - 1;
263 1         4 my $n = int( ( $loop - 2 ) / 2 );
264              
265 1 50 0     4 if ( $n > 0 ) {
    0          
266 1         3 for ( 1 .. $n ) {
267 2         6 push @l1, substr( $seq, $next5 + $_ - 2, 1 );
268 2         4 push @l2, ' ';
269 2         3 push @l3, ' ';
270 2         5 push @l4, ' ';
271 2         5 push @l5, substr( $seq, $next3 - $_, 1 );
272             }
273 1         2 $next5 += $n;
274 1         2 $next3 -= $n;
275              
276 1         2 push @l1, ' ';
277 1         2 push @l2, substr( $seq, $next5 - 1, 1 );
278 1 50       5 push @l3, $loop - 2 * ( $n + 1 ) > 0
279             ? substr( $seq, $next5, 1 )
280             : ' ';
281 1         3 push @l4, substr( $seq, $next3 + 1, 1 );
282 1         2 push @l5, ' ';
283             }
284             elsif ( $loop == 3 or $loop == 2 ) {
285 0         0 push @l1, ' ';
286 0         0 push @l2, substr( $seq, $next5 - 1, 1 );
287 0         0 push @l3, ' ';
288 0         0 push @l4, substr( $seq, $next3 + 1, 1 );
289 0         0 push @l5, ' ';
290              
291 0 0       0 if ( $loop == 3 ) {
292 0         0 push @l1, ' ';
293 0         0 push @l2, ' ';
294 0         0 push @l3, substr( $seq, $next5, 1 );
295 0         0 push @l4, ' ';
296 0         0 push @l5, ' ';
297             }
298             }
299              
300             # out put
301 1         604 my $s1 = join '', @l1;
302 1         5 my $s2 = join '', @l2;
303 1         4 my $s3 = join '', @l3;
304 1         4 my $s4 = join '', @l4;
305 1         5 my $s5 = join '', @l5;
306              
307 1         2 my $figure = '';
308 1         4 $figure .= $s1 . "\n" . $s2 . "\n" . $s3 . "\n" . $s4 . "\n" . $s5;
309              
310 1         22 return $figure;
311             }
312              
313             =head2 make_pair_table SCALAR
314              
315             Returns hash reference which represent the dot-bracket notation.
316             Table{i} is j if (i, j) pair.
317              
318             Secondary structure in dot-bracket notation is required.
319              
320             =cut
321              
322             sub make_pair_table ($) {
323 1     1 1 2 my ($struct) = @_;
324 1         1 my ( $i, $j, $length, $table, $stack );
325 1         2 $length = length $struct;
326 1         27 my @struct_data = split "", $struct;
327 1         7 for ( $i = 1; $i <= $length; $i++ ) {
328 78 100       179 if ( $struct_data[ $i - 1 ] eq '(' ) {
    100          
329 29         49 unshift @$stack, $i;
330             }
331             elsif ( $struct_data[ $i - 1 ] eq ')' ) {
332 29 50       49 if ( @$stack == 0 ) {
333 0         0 die "unbalanced brackets $struct\n";
334 0         0 return undef;
335             }
336 29         35 $j = shift @$stack;
337 29         90 $$table{$j} = $i;
338             }
339             }
340 1 50       6 if ( @$stack != 0 ) {
341 0         0 die "unbalanced brackets $struct\n";
342 0         0 return undef;
343             }
344 1         2 undef @$stack;
345 1         8 return $table;
346             }
347              
348             =head2 make_pair_table_deleting_multi_loops SCALAR
349              
350             It makes pair table for dot-bracket notation with multi loops,
351             which will be deleted and treated as a big loop, the longest
352             stem will be the final stem.
353              
354             Returns hash reference which represent the dot-bracket notation.
355             Table{i} is j if (i, j) pair.
356              
357             Secondary structure in dot-bracket notation is required.
358              
359             =cut
360              
361             sub make_pair_table_deleting_multi_loops($) {
362 0     0 1   my ($struct) = @_;
363 0           my $len = length $struct;
364 0           my @struct_data = split '', $struct;
365              
366             #==============[ find minor loops ]==============================
367 0           my (@minor_loop_sites);
368             my $site;
369 0           while ( $struct =~ /\((\.*)\)/g ) {
370 0           $site = pos $struct;
371 0           push @minor_loop_sites,
372             {
373             'start' => $site - length $1,
374             'end' => $site - 1,
375             'length' => length $1
376             };
377 0           pos $struct = $site;
378             }
379              
380             # print "$$_{start}, $$_{end}, $$_{length}\n" for @minor_loop_sites;
381              
382             #============[ make pair table for minor loops ]=================
383 0           my @tables = ();
384 0           my @visited_sites_i = ();
385 0           my @visited_sites_j = ();
386 0           my ( $i, $j );
387 0           for (@minor_loop_sites) {
388              
389             # find stem
390             # print "site: $_\n";
391 0           $i = $$_{start} - 1;
392 0           $j = $$_{end} + 1;
393              
394             # print "$i, $j, len: $len\n";
395              
396 0           my $table = {};
397 0           my $stop;
398 0   0       while ( $i >= 1 and $j <= $len ) {
399              
400             # print "->$i, $j\n";
401 0           $stop = 0;
402 0           while ( $i >= 1 ) {
403              
404             # print "i->$i, $j\n";
405 0 0         if ( $struct_data[ $i - 1 ] eq '(' ) {
    0          
406 0           last;
407             }
408             elsif ( $struct_data[ $i - 1 ] eq ')' ) {
409 0           $stop = 1;
410 0           last;
411             }
412             else {
413 0           $i--;
414             }
415             }
416 0 0         last if $stop;
417 0           while ( $j <= $len ) {
418              
419             # print "j->$i, $j\n";
420 0 0         if ( $struct_data[ $j - 1 ] eq ')' ) {
    0          
421 0           last;
422             }
423             elsif ( $struct_data[ $j - 1 ] eq '(' ) {
424 0           $stop = 1;
425 0           last;
426             }
427             else {
428 0           $j++;
429             }
430              
431             }
432 0 0         last if $stop;
433 0           $$table{$i} = $j;
434              
435             ####### for find the last stem#######
436 0           push @visited_sites_i, $i;
437 0           push @visited_sites_j, $j;
438             ####### for find the last stem#######
439              
440 0           $i--;
441 0           $j++;
442             }
443 0           push @tables, $table;
444             }
445              
446 0           $struct_data[ $_ - 1 ] = '.' for @visited_sites_i;
447 0           $struct_data[ $_ - 1 ] = '.' for @visited_sites_j;
448 0           $struct = join '', @struct_data;
449              
450             # print "$struct\n";
451              
452             #============[ make pair table for the major stem ]==============
453 0 0         if ( $struct =~ /\(\.*\)/ ) {
454 0           my $table = make_pair_table($struct);
455              
456             # delete the weird stem
457 0           my @left = sort { $a <=> $b } keys %$table;
  0            
458 0           my @right = map { $$table{$_} } @left;
  0            
459 0           my ( @delete_i, @delete_j );
460 0           my ( $a, $b, $i, $j );
461              
462 0           foreach $b (@right) {
463 0           foreach $j (@visited_sites_j) {
464 0 0         if ( $b < $j ) {
465 0           push @delete_j, $b;
466 0           last;
467             }
468             }
469             }
470              
471 0           foreach $a (@left) {
472 0           foreach $i (@visited_sites_i) {
473 0 0         if ( $a > $i ) {
474 0           push @delete_i, $a;
475             }
476             }
477             }
478 0           delete $$table{$_} for @delete_i;
479 0           for ( keys %$table ) {
480 0 0         if ( $$table{$_} ~~ @delete_j ) {
481 0           delete $$table{$_};
482             }
483             }
484              
485 0           push @tables, $table;
486             }
487              
488             #
489             # print "sites of every stems\n";
490             # for (@tables) {
491             # my $table = $_;
492             # my @left = sort{ $a <=> $b } keys %$table;
493             # my @right = map { $$table{$_} } @left;
494             # print "@left\n";
495             # print "@right\n";
496             # }
497              
498             #============[ find the longest stem ]===========================
499 0           my $stem_length_max = 0;
500 0           my $longest_table;
501             my $n;
502 0           for (@tables) {
503 0           $n = scalar keys %$_;
504              
505             # print "length: $n\n";
506 0 0         if ( $n > $stem_length_max ) {
507 0           $stem_length_max = $n;
508 0           $longest_table = $_;
509             }
510             }
511 0           return $longest_table;
512             }
513              
514             =head1 AUTHOR
515              
516             Wei Shen, C<< >>
517              
518             =head1 BUGS
519              
520             Please report any bugs or feature requests to C, or through
521             the web interface at L. I will be notified, and then you'll
522             automatically be notified of progress on your bug as I make changes.
523              
524              
525              
526              
527             =head1 SUPPORT
528              
529             You can find documentation for this module with the perldoc command.
530              
531             perldoc RNA::HairpinFigure
532              
533              
534             You can also look for information at:
535              
536             =over 4
537              
538             =item * RT: CPAN's request tracker (report bugs here)
539              
540             L
541              
542             =item * AnnoCPAN: Annotated CPAN documentation
543              
544             L
545              
546             =item * CPAN Ratings
547              
548             L
549              
550             =item * Search CPAN
551              
552             L
553              
554             =back
555              
556              
557             =head1 ACKNOWLEDGEMENTS
558              
559              
560             =head1 LICENSE AND COPYRIGHT
561              
562             Copyright 2013 Wei Shen.
563              
564             This program is free software; you can redistribute it and/or modify it
565             under the terms of the the Artistic License (2.0). You may obtain a
566             copy of the full license at:
567              
568             L
569              
570             Any use, modification, and distribution of the Standard or Modified
571             Versions is governed by this Artistic License. By using, modifying or
572             distributing the Package, you accept this license. Do not use, modify,
573             or distribute the Package, if you do not accept this license.
574              
575             If your Modified Version has been derived from a Modified Version made
576             by someone other than you, you are nevertheless required to ensure that
577             your Modified Version complies with the requirements of this license.
578              
579             This license does not grant you the right to use any trademark, service
580             mark, tradename, or logo of the Copyright Holder.
581              
582             This license includes the non-exclusive, worldwide, free-of-charge
583             patent license to make, have made, use, offer to sell, sell, import and
584             otherwise transfer the Package with respect to any patent claims
585             licensable by the Copyright Holder that are necessarily infringed by the
586             Package. If you institute patent litigation (including a cross-claim or
587             counterclaim) against any party alleging that the Package constitutes
588             direct or contributory patent infringement, then this Artistic License
589             to you shall terminate on the date that such litigation is filed.
590              
591             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
592             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
593             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
594             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
595             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
596             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
597             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
598             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
599              
600              
601             =cut
602              
603             1; # End of RNA::HairpinFigure