File Coverage

blib/lib/RNA/HairpinFigure.pm
Criterion Covered Total %
statement 124 245 50.6
branch 24 58 41.3
condition 9 21 42.8
subroutine 6 7 85.7
pod 3 3 100.0
total 166 334 49.7


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